<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222"><meta name="generator" content="Hexo 6.3.0">

  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha256-Z1K5uhUaJXA7Ll0XrZ/0JhX4lAtZFpT6jkKrEDT0drU=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"example.com","root":"/","images":"/images","scheme":"Muse","darkmode":false,"version":"8.14.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":false,"style":null},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":-1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>

    <meta property="og:type" content="website">
<meta property="og:title" content="JsyBlog">
<meta property="og:url" content="http://example.com/page/13/index.html">
<meta property="og:site_name" content="JsyBlog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="SongyangJi">
<meta name="twitter:card" content="summary">


<link rel="canonical" href="http://example.com/page/13/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-CN","comments":"","permalink":"","path":"page/13/index.html","title":""}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>JsyBlog</title>
  








  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <div class="column">
      <header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">JsyBlog</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger" aria-label="搜索" role="button">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu"><li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li><li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li><li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li><li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</header>
        
  
  <aside class="sidebar">

    <div class="sidebar-inner sidebar-overview-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">SongyangJi</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">251</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">45</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">109</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>

        </div>
      </div>
    </div>

    
  </aside>


    </div>

    <div class="main-inner index posts-expand">

    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/" class="post-title-link" itemprop="url">Redis底层数据结构</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-19 16:31:28" itemprop="dateCreated datePublished" datetime="2022-03-19T16:31:28+08:00">2022-03-19</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-09-02 22:44:06" itemprop="dateModified" datetime="2022-09-02T22:44:06+08:00">2022-09-02</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Redis/" itemprop="url" rel="index"><span itemprop="name">Redis</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p><strong>文章结构</strong></p>
<img src="overview2.jpg" style="zoom:50%;" />





<h1 id="Redis的数据类型和底层数据结构"><a href="#Redis的数据类型和底层数据结构" class="headerlink" title="Redis的数据类型和底层数据结构"></a>Redis的数据类型和底层数据结构</h1><p><strong>Redis 数据结构并不是指 String（字符串）对象、List（列表）对象、Hash（哈希）对象、Set（集合）对象和 Zset（有序集合）对象，因为这些是 Redis 键值对中值的数据类型，也就是数据的保存形式，这些对象的底层实现的方式就用到了数据结构</strong>。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/overview1.jpg"></p>
<blockquote>
<p>上图中，zset的实现箭头有误。应该使用到了 [压缩列表 &#x2F; listpack] or [哈希表 + 跳表]，也就是说图中少画了一条指向哈希表的箭头。<a target="_blank" rel="noopener" href="https://developer.aliyun.com/article/666398">redis zset底层数据结构</a> </p>
</blockquote>
<ul>
<li>在 Redis 3.0 版本中 List 对象的底层数据结构由「双向链表」或「压缩表列表」实现，但是在 3.2 版本之后，List 数据类型底层数据结构是由 quicklist 实现的；</li>
<li>在最新的 Redis 代码（还未发布正式版本）中，压缩列表数据结构已经废弃了，交由 listpack 数据结构来实现了。</li>
</ul>
<h1 id="KV数据库的整体设计"><a href="#KV数据库的整体设计" class="headerlink" title="KV数据库的整体设计"></a>KV数据库的整体设计</h1><p>在开始讲数据结构之前，先给介绍下 Redis 是怎样实现键值对（key-value）数据库的。</p>
<p>Redis 的键值对中的 key 就是字符串对象，而 <strong>value 可以是字符串对象，也可以是集合数据类型的对象</strong>，比如 List 对象、Hash 对象、Set 对象和 Zset 对象。</p>
<p><strong>Redis 是使用了一个「哈希表」保存所有键值对</strong>，哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对。哈希表其实就是一个数组，数组中的元素叫做哈希桶。</p>
<p>Redis 的哈希桶是怎么保存键值对数据的呢？</p>
<p>哈希桶存放的是指向键值对数据的指针（dictEntry*），这样通过指针就能找到键值对数据，然后因为键值对的值可以保存字符串对象和集合数据类型的对象，所以键值对的数据结构中并不是直接保存值本身，而是保存了 </p>
<p>void * key 和 void * value 指针，分别指向了实际的键对象和值对象，这样一来，即使值是集合数据，也可以通过 void * value 指针找到。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/overview3.jpg"></p>
<ul>
<li>redisDb 结构，表示 Redis 数据库的结构，结构体里存放了指向了 dict 结构的指针；</li>
<li>dict 结构，结构体里存放了 2 个哈希表，正常情况下都是用「哈希表1」，「哈希表2」只有在 rehash 的时候才用，具体什么是 rehash，我在本文的哈希表数据结构会讲；</li>
<li>ditctht 结构，表示哈希表的结构，结构里存放了哈希表数组，数组中的每个元素都是指向一个哈希表节点结构（dictEntry）的指针；</li>
<li>dictEntry 结构，表示哈希表节点的结构，结构里存放了 <strong>void * key 和 void * value 指针， *key 指向的是 String 对象，而 *value 则可以指向 String 对象，也可以指向集合类型的对象，比如 List 对象、Hash 对象、Set 对象和 Zset 对象</strong>。</li>
</ul>
<p>特别说明下，void * key 和 void * value 指针指向的是 <strong>Redis 对象</strong>，Redis 中的每个对象都由 redisObject 结构表示，如下图：</p>
<img src="redis_object.jpg" style="zoom:70%;" />



<p>对象结构里包含的成员变量：</p>
<ul>
<li>type，标识该对象是什么类型的对象（String 对象、 List 对象、Hash 对象、Set 对象和 Zset 对象）；</li>
<li>encoding，标识该对象使用了哪种底层的数据结构；</li>
<li><strong>ptr，指向底层数据结构的指针</strong>。</li>
</ul>
<h1 id="SDS（simple-dynamic-string）"><a href="#SDS（simple-dynamic-string）" class="headerlink" title="SDS（simple dynamic string）"></a>SDS（simple dynamic string）</h1><p>由于C 语言字符串的缺陷，Redis 是用 C 语言实现的，但是它没有直接使用 C 语言的 char* 字符数组来实现字符串，而是自己封装了一个名为简单动态字符串（simple dynamic string，SDS） 的数据结构来表示字符串，也就是 Redis 的 String 数据类型的底层数据结构是 SDS。</p>
<ol>
<li>O（1）复杂度获取字符串长度</li>
<li>二进制安全</li>
<li>不会发生缓冲区溢出</li>
</ol>
<h1 id="双向链表"><a href="#双向链表" class="headerlink" title="双向链表"></a>双向链表</h1><p>Redis对双向链表的实现和我们一般自己实现的并没有什么区别：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> &#123;</span></span><br><span class="line">    <span class="comment">//前置节点</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">prev</span>;</span></span><br><span class="line">    <span class="comment">//后置节点</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">next</span>;</span></span><br><span class="line">    <span class="comment">//节点的值</span></span><br><span class="line">    <span class="type">void</span> *value;</span><br><span class="line">&#125; listNode;</span><br></pre></td></tr></table></figure>

<p>它的特性我们都很清楚。</p>
<blockquote>
<p>链表的缺陷也是有的：</p>
<ul>
<li>链表每个节点之间的内存都是不连续的，意味着<strong>无法很好利用 CPU 缓存</strong>。能很好利用 CPU 缓存的数据结构就是数组，因为数组的内存是连续的，这样就可以充分利用 CPU 缓存来加速访问。</li>
<li>还有一点，保存一个链表节点的值都需要一个链表节点结构头的分配，<strong>内存开销较大</strong>。</li>
</ul>
</blockquote>
<h1 id="压缩列表"><a href="#压缩列表" class="headerlink" title="压缩列表"></a>压缩列表</h1><p>压缩列表的最大特点，就是它被设计成一种内存紧凑型的数据结构，占用一块连续的内存空间，不仅可以利用 CPU 缓存，而且会针对不同长度的数据，进行相应编码，这种方法可以有效地节省内存开销。</p>
<p>压缩列表的最大特点，就是它被设计成一种内存紧凑型的数据结构，占用一块连续的内存空间，不仅可以利用 CPU 缓存，而且会针对不同长度的数据，进行相应编码，这种方法可以有效地节省内存开销。</p>
<p>但是，压缩列表的缺陷也是有的：</p>
<ul>
<li><strong>不能保存过多的元素，否则查询效率就会降低</strong>；</li>
<li><strong>新增或修改某个元素时，压缩列表占用的内存空间需要重新分配，甚至可能引发连锁更新的问题</strong>。</li>
</ul>
<h2 id="压缩列表结构设计"><a href="#压缩列表结构设计" class="headerlink" title="压缩列表结构设计"></a>压缩列表结构设计</h2><p>压缩列表是 Redis 为了节约内存而开发的，它是<strong>由连续内存块组成的顺序型数据结构</strong>，有点类似于数组。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%8E%8B%E7%BC%A9%E5%88%97%E8%A1%A81.png" alt="图片"></p>
<p>压缩列表在表头有三个字段：</p>
<ul>
<li><strong>zlbytes</strong>，记录整个压缩列表占用内存字节数；</li>
<li><strong>zltail</strong>，记录压缩列表「尾部」节点距离起始地址多少字节，也就是列表尾的偏移量；</li>
<li><strong>zllen</strong>，<strong>记录压缩列表包含的节点数量</strong>；</li>
<li><strong>zlend</strong>，标记压缩列表的结束点，固定值 0xFF（十进制255）。</li>
</ul>
<p>在压缩列表中，如果我们要查找定位第一个元素和最后一个元素，可以通过表头三个字段的长度直接定位，复杂度是 O(1)。而<strong>查找其他元素时，就没有这么高效了，只能逐个查找，此时的复杂度就是 O(N) 了，因此压缩列表不适合保存过多的元素</strong>。</p>
<p>另外，压缩列表节点（entry）的构成如下：</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E5%8E%8B%E7%BC%A9%E5%88%97%E8%A1%A82.png" alt="图片"></p>
<p>压缩列表节点包含三部分内容：</p>
<ul>
<li><strong>prevlen</strong>，记录了「前一个节点」的长度；</li>
<li><strong>encoding</strong>，记录了当前节点实际数据的类型以及长度；</li>
<li><strong>data</strong>，记录了当前节点的实际数据；</li>
</ul>
<p>当我们往压缩列表中插入数据时，压缩列表就会根据数据是字符串还是整数，以及数据的大小，会使用不同空间大小的 prevlen 和 encoding 这两个元素里保存的信息，<strong>这种根据数据大小和类型进行不同的空间大小分配的设计思想，正是 Redis 为了节省内存而采用的</strong>。</p>
<p>分别说下，prevlen 和 encoding 是如何根据数据的大小和类型来进行不同的空间大小分配。</p>
<p>压缩列表里的每个节点中的  prevlen 属性都记录了「前一个节点的长度」，而且 prevlen 属性的空间大小跟前一个节点长度值有关，比如：</p>
<ul>
<li>如果<strong>前一个节点的长度小于 254 字节</strong>，那么 prevlen 属性需要用 <strong>1 字节的空间</strong>来保存这个长度值；</li>
<li>如果<strong>前一个节点的长度大于等于 254 字节</strong>，那么 prevlen 属性需要用 <strong>5 字节的空间</strong>来保存这个长度值；</li>
</ul>
<p>encoding 属性的空间大小跟数据是字符串还是整数，以及字符串的长度有关：</p>
<ul>
<li>如果<strong>当前节点的数据是整数</strong>，则 encoding 会使用 <strong>1 字节的空间</strong>进行编码。</li>
<li>如果<strong>当前节点的数据是字符串，根据字符串的长度大小</strong>，encoding 会使用 <strong>1 字节&#x2F;2字节&#x2F;5字节的空间</strong>进行编码。</li>
</ul>
<p>因此，<strong>压缩列表只会用于保存的节点数量不多的场景</strong>，只要节点数量足够小，即使发生连锁更新，也是能接受的。</p>
<blockquote>
<p>虽说如此，Redis 针对压缩列表在设计上的不足，在后来的版本中，新增设计了两种数据结构：quicklist（Redis 3.2 引入） 和 listpack（Redis 5.0 引入）。<strong>这两种数据结构的设计目标，就是尽可能地保持压缩列表节省内存的优势，同时解决压缩列表的「连锁更新」的问题</strong>。</p>
</blockquote>
<h2 id="压缩列表的缺陷"><a href="#压缩列表的缺陷" class="headerlink" title="压缩列表的缺陷"></a>压缩列表的缺陷</h2><p>空间扩展操作也就是重新分配内存，因此<strong>连锁更新一旦发生，就会导致压缩列表占用的内存空间要多次重新分配，这就会直接影响到压缩列表的访问性能</strong>。</p>
<p>所以说，<strong>虽然压缩列表紧凑型的内存布局能节省内存开销，但是如果保存的元素数量增加了，或是元素变大了，会导致内存重新分配，最糟糕的是会有「连锁更新」的问题</strong>。</p>
<h1 id="quicklist"><a href="#quicklist" class="headerlink" title="quicklist"></a>quicklist</h1><blockquote>
<p>在 Redis 3.0 之前，List 对象的底层数据结构是双向链表或者压缩列表。然后在  Redis 3.2 的时候，List 对象的底层改由 quicklist 数据结构实现。</p>
</blockquote>
<p>在 Redis 3.0 之前，List 对象的底层数据结构是双向链表或者压缩列表。然后在  Redis 3.2 的时候，List 对象的底层改由 quicklist 数据结构实现。</p>
<p>其实 <strong>quicklist 就是「双向链表 + 压缩列表」组合，因为一个 quicklist 就是一个链表，而链表中的每个元素又是一个压缩列表</strong>。</p>
<p>在前面讲压缩列表的时候，我也提到了压缩列表的不足，虽然压缩列表是通过紧凑型的内存布局节省了内存开销，但是因为它的结构设计，如果保存的元素数量增加，或者元素变大了，压缩列表会有「连锁更新」的风险，一旦发生，会造成性能下降。</p>
<p>quicklist 解决办法，<strong>通过控制每个链表节点中的压缩列表的大小或者元素个数，来规避连锁更新的问题。因为压缩列表元素越少或越小，连锁更新带来的影响就越小，从而提供了更好的访问性能。</strong></p>
<h4 id="quicklist-结构设计"><a href="#quicklist-结构设计" class="headerlink" title="quicklist 结构设计"></a>quicklist 结构设计</h4><p>quicklist 的结构体跟链表的结构体类似，都包含了表头和表尾，区别在于 quicklist 的节点是 quicklistNode。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">quicklist</span> &#123;</span></span><br><span class="line">    <span class="comment">//quicklist的链表头</span></span><br><span class="line">    quicklistNode *head;      <span class="comment">//quicklist的链表头</span></span><br><span class="line">    <span class="comment">//quicklist的链表头</span></span><br><span class="line">    quicklistNode *tail; </span><br><span class="line">    <span class="comment">//所有压缩列表中的总元素个数</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> count;</span><br><span class="line">    <span class="comment">//quicklistNodes的个数</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> len;       </span><br><span class="line">    ...</span><br><span class="line">&#125; quicklist;</span><br></pre></td></tr></table></figure>

<p>接下来看看，quicklistNode 的结构定义：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> &#123;</span></span><br><span class="line">    <span class="comment">//前一个quicklistNode</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> *<span class="title">prev</span>;</span>     <span class="comment">//前一个quicklistNode</span></span><br><span class="line">    <span class="comment">//下一个quicklistNode</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">quicklistNode</span> *<span class="title">next</span>;</span>     <span class="comment">//后一个quicklistNode</span></span><br><span class="line">    <span class="comment">//quicklistNode指向的压缩列表</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> *zl;              </span><br><span class="line">    <span class="comment">//压缩列表的的字节大小</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> sz;                </span><br><span class="line">    <span class="comment">//压缩列表的元素个数</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">int</span> count : <span class="number">16</span>;        <span class="comment">//ziplist中的元素个数 </span></span><br><span class="line">    ....</span><br><span class="line">&#125; quicklistNode;</span><br></pre></td></tr></table></figure>

<p>可以看到，quicklistNode 结构体里包含了前一个节点和下一个节点指针，这样每个 quicklistNode 形成了一个双向链表。但是链表节点的元素不再是单纯保存元素值，而是保存了一个压缩列表，所以 quicklistNode 结构体里有个指向压缩列表的指针 *zl。</p>
<p>我画了一张图，方便你理解 quicklist 数据结构。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/quicklist.png" alt="图片"></p>
<p>在向 quicklist 添加一个元素的时候，不会像普通的链表那样，直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素，如果能容纳就直接保存到 quicklistNode 结构里的压缩列表，如果不能容纳，才会新建一个新的 quicklistNode 结构。</p>
<p>quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数，来规避潜在的连锁更新的风险，但是这并没有完全解决连锁更新的问题。</p>
<h1 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h1><p><strong>Redis 采用了「链式哈希」来解决哈希冲突</strong>，在不扩容哈希表的前提下，将具有相同哈希值的数据串起来，形成链接起，以便这些数据在表中仍然可以被查询到。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictht</span> &#123;</span></span><br><span class="line">    <span class="comment">//哈希表数组</span></span><br><span class="line">    dictEntry **table;</span><br><span class="line">    <span class="comment">//哈希表大小</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> size;  </span><br><span class="line">    <span class="comment">//哈希表大小掩码，用于计算索引值</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> sizemask;</span><br><span class="line">    <span class="comment">//该哈希表已有的节点数量</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> used;</span><br><span class="line">&#125; dictht;</span><br></pre></td></tr></table></figure>

<p>可以看到，哈希表是一个数组（dictEntry **table），数组的每个元素是一个指向「哈希表节点（dictEntry）」的指针。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/hash1.png" alt="图片"></p>
<p>哈希表节点的结构如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> &#123;</span></span><br><span class="line">    <span class="comment">//键值对中的键</span></span><br><span class="line">    <span class="type">void</span> *key;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//键值对中的值</span></span><br><span class="line">    <span class="class"><span class="keyword">union</span> &#123;</span></span><br><span class="line">        <span class="type">void</span> *val;</span><br><span class="line">        <span class="type">uint64_t</span> u64;</span><br><span class="line">        <span class="type">int64_t</span> s64;</span><br><span class="line">        <span class="type">double</span> d;</span><br><span class="line">    &#125; v;</span><br><span class="line">    <span class="comment">//指向下一个哈希表节点，形成链表</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">dictEntry</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125; dictEntry;</span><br></pre></td></tr></table></figure>

<p>dictEntry 结构里不仅包含指向键和值的指针，还包含了指向下一个哈希表节点的指针，这个指针可以将多个哈希值相同的键值对链接起来，以此来解决哈希冲突的问题，这就是链式哈希。</p>
<p>另外，这里还跟你提一下，dictEntry 结构里键值对中的值是一个「联合体 v」定义的，因此，键值对中的值可以是一个指向实际值的指针，或者是一个无符号的 64 位整数或有符号的 64 位整数或double 类的值。这么做的好处是可以节省内存空间，因为当「值」是整数或浮点数时，就可以将值的数据内嵌在 dictEntry 结构里，无需再用一个指针指向实际的值，从而节省了内存空间。</p>
<h2 id="渐进式rehash"><a href="#渐进式rehash" class="headerlink" title="渐进式rehash"></a>渐进式rehash</h2><p>哈希表结构设计的这一小节，我给大家介绍了 Redis 使用 dictht 结构体表示哈希表。不过，在实际使用哈希表时，Redis 定义一个 dict 结构体，这个结构体里定义了<strong>两个哈希表（ht[2]）</strong>。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">dict</span> &#123;</span></span><br><span class="line">    …</span><br><span class="line">    <span class="comment">//两个Hash表，交替使用，用于rehash操作</span></span><br><span class="line">    dictht ht[<span class="number">2</span>]; </span><br><span class="line">    …</span><br><span class="line">&#125; dict;</span><br></pre></td></tr></table></figure>



<p>之所以定义了 2 个哈希表，是因为进行 rehash 的时候，需要用上 2 个哈希表了。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/hash1.png"></p>
<p>在正常服务请求阶段，插入的数据，都会写入到「哈希表 1」，此时的「哈希表 2 」 并没有被分配空间。</p>
<p>随着数据逐步增多，触发了 rehash 操作，这个过程分为三步：</p>
<ul>
<li>给「哈希表 2」 分配空间，一般会比「哈希表 1」 大 2 倍；</li>
<li>将「哈希表 1 」的数据迁移到「哈希表 2」 中；</li>
<li>迁移完成后，「哈希表 1 」的空间会被释放，并把「哈希表 2」 设置为「哈希表 1」，然后在「哈希表 2」 新创建一个空白的哈希表，为下次 rehash 做准备。</li>
</ul>
<p>为了方便你理解，我把 rehash 这三个过程画在了下面这张图：</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/hash2.png" alt="图片"></p>
<p>这个过程看起来简单，但是其实第二步很有问题，<strong>如果「哈希表 1 」的数据量非常大，那么在迁移至「哈希表 2 」的时候，因为会涉及大量的数据拷贝，此时可能会对 Redis 造成阻塞，无法服务其他请求</strong>。</p>
<p>为了避免 rehash 在数据迁移过程中，因拷贝数据的耗时，影响 Redis 性能的情况，所以 Redis 采用了<strong>渐进式 rehash</strong>，也就是将数据的迁移的工作不再是一次性迁移完成，而是分多次迁移。</p>
<p>渐进式 rehash 步骤如下：</p>
<ul>
<li>给「哈希表 2」 分配空间；</li>
<li><strong>在 rehash 进行期间，每次哈希表元素进行新增、删除、查找或者更新操作时，Redis 除了会执行对应的操作之外，还会顺序将「哈希表 1 」中索引位置上的所有 key-value 迁移到「哈希表 2」 上</strong>；</li>
<li>随着处理客户端发起的哈希表操作请求数量越多，最终在某个时间嗲呢，会把「哈希表 1 」的所有 key-value 迁移到「哈希表 2」，从而完成 rehash 操作。</li>
</ul>
<p><strong>这样就巧妙地把一次性大量数据迁移工作的开销，分摊到了多次处理请求的过程中，避免了一次性 rehash 的耗时操作</strong>。</p>
<p><strong>在进行渐进式 rehash 的过程中，会有两个哈希表，所以在渐进式 rehash 进行期间，哈希表元素的删除、查找、更新等操作都会在这两个哈希表进行。</strong></p>
<p>比如，查找一个 key 的值的话，先会在「哈希表 1」 里面进行查找，如果没找到，就会继续到哈希表 2 里面进行找到。</p>
<p>另外，<strong>在渐进式 rehash 进行期间，新增一个 key-value 时，会被保存到「哈希表 2 」里面，而「哈希表 1」 则不再进行任何添加操作，这样保证了「哈希表 1 」的 key-value 数量只会减少</strong>，随着 rehash 操作的完成，最终「哈希表 1 」就会变成空表。</p>
<p><strong>rehash 触发条件</strong><br>触发 rehash 操作的条件，主要有两个：</p>
<ol>
<li>当负载因子大于等于 1 ，并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令，也就是没有执行 RDB 快照或没有进行 AOF 重写的时候，就会进行 rehash 操作。</li>
<li>当负载因子大于等于 5 时，此时说明哈希冲突非常严重了，不管有没有有在执行 RDB 快照或 AOF 重写，都会强制进行 rehash 操作。</li>
</ol>
<p>（负载因子&#x3D;节点数&#x2F;哈希表容量）</p>
<h1 id="跳表"><a href="#跳表" class="headerlink" title="跳表"></a>跳表</h1><p>Redis 只有在 Zset 对象的底层实现用到了跳表，跳表的优势是能支持平均 O(logN) 复杂度的节点查找。</p>
<p>Zset 对象是唯一一个同时使用了两个数据结构来实现的 Redis 对象，这两个数据结构一个是跳表，一个是哈希表。这样的好处是既能进行高效的范围查询，也能进行高效单点查询。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zset</span> &#123;</span></span><br><span class="line">    dict *dict;     <span class="comment">// map用来存储member到score的映射，这样就实现了set部分的功能；并可以在O(1)时间内找到member对应的分数；</span></span><br><span class="line">    zskiplist *zsl; <span class="comment">// skiplist按score从小到大的顺序存储节点</span></span><br><span class="line">&#125; zset;</span><br></pre></td></tr></table></figure>



<p>Zset 对象能支持范围查询（如 ZRANGEBYSCORE 操作），这是因为它的数据结构设计采用了跳表，而又能以常数复杂度获取元素权重（如 ZSCORE 操作），这是因为它同时采用了哈希表进行索引。</p>
<h2 id="跳表结构设计"><a href="#跳表结构设计" class="headerlink" title="跳表结构设计"></a>跳表结构设计</h2><p>链表在查找元素的时候，因为需要逐一查找，所以查询效率非常低，时间复杂度是O(N)，于是就出现了跳表。<strong>跳表是在链表基础上改进过来的，实现了一种「多层」的有序链表</strong>，这样的好处是能快读定位数据。</p>
<p>那跳表长什么样呢？我这里举个例子，下图展示了一个层级为 3 的跳表。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/J0g14CUwaZc0xXMUofrwAVS3mR0DxcWCQibqN7sJ9zUaiaE6pkqs2gKDw3GKFibiaK5EOTq5m7Sdnpplfht6Y8ia8Bg/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1" alt="图片"></p>
<p>图中头节点有 L0~L2 三个头指针，分别指向了不同层级的节点，然后每个层级的节点都通过指针连接起来：</p>
<ul>
<li>L0 层级共有 5 个节点，分别是节点1、2、3、4、5；</li>
<li>L1 层级共有 3 个节点，分别是节点 2、3、5；</li>
<li>L2 层级只有 1 个节点，也就是节点 3 。</li>
</ul>
<p>如果我们要在链表中查找节点 4 这个元素，只能从头开始遍历链表，需要查找 4 次，而使用了跳表后，只需要查找 2 次就能定位到节点 4，因为可以在头节点直接从 L2 层级跳到节点 3，然后再往前遍历找到节点 4。</p>
<p>可以看到，这个查找过程就是在多个层级上跳来跳去，最后定位到元素。当数据量很大时，跳表的查找复杂度就是 O(logN)。</p>
<p>那跳表节点是怎么实现多层级的呢？这就需要看「跳表节点」的数据结构了，如下：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> &#123;</span></span><br><span class="line">    <span class="comment">//Zset 对象的元素值</span></span><br><span class="line">    sds ele;</span><br><span class="line">    <span class="comment">//元素权重值</span></span><br><span class="line">    <span class="type">double</span> score;</span><br><span class="line">    <span class="comment">//后向指针</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">backward</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//节点的level数组，保存每层上的前向指针和跨度</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistLevel</span> &#123;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">forward</span>;</span></span><br><span class="line">        <span class="type">unsigned</span> <span class="type">long</span> span;</span><br><span class="line">    &#125; level[];</span><br><span class="line">&#125; zskiplistNode;</span><br></pre></td></tr></table></figure>



<p>Zset 对象要同时保存元素和元素的权重，对应到跳表节点结构里就是 sds 类型的 ele 变量和 double 类型的 score 变量。<strong>每个跳表节点都有一个后向指针，指向前一个节点，目的是为了方便从跳表的尾节点开始访问节点，这样倒序查找时很方便</strong>。</p>
<p>跳表是一个带有层级关系的链表，而且每一层级可以包含多个节点，每一个节点通过指针连接起来，实现这一特性就是靠跳表节点结构体中的<strong>zskiplistLevel 结构体类型的 level 数组</strong>。</p>
<p>level 数组中的每一个元素代表跳表的一层，也就是由 zskiplistLevel 结构体表示，比如 leve[0] 就表示第一层，leve[1] 就表示第二层。zskiplistLevel 结构体里定义了「指向下一个跳表节点的指针」和「跨度」，跨度时用来记录两个节点之间的距离。</p>
<p>比如，下面这张图，展示了各个节点的跨度。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/zset1.png" alt="图片"></p>
<p>第一眼看到跨度的时候，以为是遍历操作有关，实际上并没有任何关系，遍历操作只需要用前向指针就可以完成了。</p>
<p><strong>跨度实际上是为了计算这个节点在跳表中的排名</strong>。具体怎么做的呢？因为跳表中的节点都是按序排列的，那么计算某个节点排位的时候，从头节点点到该结点的查询路径上，将沿途访问过的所有层的跨度累加起来，得到的结果就是目标节点在跳表中的排位。</p>
<p>举个例子，查找图中节点 3 在跳表中的排位，从头节点开始查找节点 3，查找的过程只经过了一个层（L3），并且层的跨度是 3，所以节点 3 在跳表中的排位是 3。</p>
<p>另外，图中的头节点其实也是 zskiplistNode 跳表节点，只不过头节点的后向指针、权重、元素值都会被用到，所以图中省略了这部分。</p>
<p>问题来了，由谁定义哪个跳表节点是头节点呢？这就介绍「跳表」结构体了，如下所示：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">zskiplist</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">zskiplistNode</span> *<span class="title">header</span>, *<span class="title">tail</span>;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">long</span> length;</span><br><span class="line">    <span class="type">int</span> level;</span><br><span class="line">&#125; zskiplist;</span><br></pre></td></tr></table></figure>

<p>跳表结构里包含了：</p>
<ul>
<li>跳表的头尾节点，便于在O(1)时间复杂度内访问跳表的头节点和尾节点；</li>
<li>跳表的长度，便于在O(1)时间复杂度获取跳表节点的数量；</li>
<li>跳表的最大层数，便于在O(1)时间复杂度获取跳表中层高最大的那个节点的层数量；</li>
<li></li>
</ul>
<h2 id="跳表节点查询过程"><a href="#跳表节点查询过程" class="headerlink" title="跳表节点查询过程"></a>跳表节点查询过程</h2><p>查找一个跳表节点的过程时，跳表会从头节点的最高层开始，逐一遍历每一层。在遍历某一层的跳表节点时，会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断，共有两个判断条件：</p>
<ul>
<li>如果当前节点的权重「小于」要查找的权重时，跳表就会访问该层上的下一个节点。</li>
<li>如果当前节点的权重「等于」要查找的权重时，并且当前节点的 SDS 类型数据「小于」要查找的数据时，跳表就会访问该层上的下一个节点。</li>
</ul>
<p>如果上面两个条件都不满足，或者下一个节点为空时，跳表就会使用目前遍历到的节点的 level 数组里的下一层指针，然后沿着下一层指针继续查找，这就相当于跳到了下一层接着查找。</p>
<p>举个例子，下图有个 3 层级的跳表。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/zset2.png" alt="图片"></p>
<p>如果要查找「元素：abcd，权重：4」的节点，查找的过程是这样的：</p>
<ul>
<li>先从头节点的最高层开始，L2 指向了「元素：abc，权重：3」节点，这个节点的权重比要查找节点的小，所以要访问该层上的下一个节点；</li>
<li>但是该层上的下一个节点是空节点，于是就会跳到「元素：abc，权重：3」节点的下一层去找，也就是 leve[1];</li>
<li>「元素：abc，权重：3」节点的  leve[1] 的下一个指针指向了「元素：abcde，权重：4」的节点，然后将其和要查找的节点比较。虽然「元素：abcde，权重：4」的节点的权重和要查找的权重相同，但是当前节点的 SDS 类型数据「大于」要查找的数据，所以会继续跳到「元素：abc，权重：3」节点的下一层去找，也就是 leve[0]；</li>
<li>「元素：abc，权重：3」节点的 leve[0] 的下一个指针指向了「元素：abcd，权重：4」的节点，该节点正是要查找的节点，查询结束。</li>
</ul>
<h2 id="跳表节点层数设置"><a href="#跳表节点层数设置" class="headerlink" title="跳表节点层数设置"></a>跳表节点层数设置</h2><p>跳表的相邻两层的节点数量的比例会影响跳表的查询性能。</p>
<p>举个例子，下图的跳表，第二层的节点数量只有 1 个，而第一层的节点数量有 6 个。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/zset3.png" alt="图片"></p>
<p>这时，如果想要查询节点 6，那基本就跟链表的查询复杂度一样，就需要在第一层的节点中依次顺序查找，复杂度就是 O(N) 了。所以，为了降低查询复杂度，我们就需要维持相邻层结点数间的关系。</p>
<p>**跳表的相邻两层的节点数量最理想的比例是 2:1，查找复杂度可以降低到 O(logN)**。</p>
<p>下图的跳表就是，相邻两层的节点数量的比例是 2 : 1。</p>
<p><img src="/2022/03/19/Redis%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/zset4.png" alt="图片"></p>
<blockquote>
<p>那怎样才能维持相邻两层的节点数量的比例为 2 : 1  呢？</p>
</blockquote>
<p>如果采用新增节点或者删除节点时，来调整跳表节点以维持比例的方法的话，会带来额外的开销。</p>
<p>Redis 则采用一种巧妙的方法是，<strong>跳表在创建节点的时候，随机生成每个节点的层数</strong>，并没有严格维持相邻两层的节点数量比例为 2 : 1 的情况。</p>
<p>具体的做法是，<strong>跳表在创建节点时候，会生成范围为[0-1]的一个随机数，如果这个随机数小于 0.25（相当于概率 25%），那么层数就增加 1 层，然后继续生成下一个随机数，直到随机数的结果大于 0.25 结束，最终确定该节点的层数</strong>。</p>
<p>这样的做法，相当于每增加一层的概率不超过 25%，层数越高，概率越低，层高最大限制是 64。</p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/select%E3%80%81poll%E3%80%81epoll/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/select%E3%80%81poll%E3%80%81epoll/" class="post-title-link" itemprop="url">《Unix网络编程》—— echo server的bio、select、poll、epoll多种实现</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-16 23:13:27" itemprop="dateCreated datePublished" datetime="2022-03-16T23:13:27+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-17 01:09:56" itemprop="dateModified" datetime="2022-03-17T01:09:56+08:00">2022-03-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/IO/" itemprop="url" rel="index"><span itemprop="name">IO</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="blocked-io"><a href="#blocked-io" class="headerlink" title="blocked io"></a>blocked io</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/11/18</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>    <span class="string">&quot;../lib/unp.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">str_echo</span><span class="params">(<span class="type">int</span> sockfd)</span> &#123;</span><br><span class="line">    <span class="type">int</span> bytes;</span><br><span class="line">    <span class="type">char</span> buf[MAXLINE] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="comment">// RECEIVE</span></span><br><span class="line">        bytes = read(sockfd, buf, MAXLINE);</span><br><span class="line">        <span class="keyword">if</span> (bytes &lt;= <span class="number">0</span>) <span class="keyword">break</span>;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;server receive is %s\n&quot;</span>, buf);</span><br><span class="line">        <span class="comment">// SEND</span></span><br><span class="line">        write(sockfd, buf, bytes);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;server start...\n&quot;</span>);</span><br><span class="line">    <span class="type">int</span> listenfd, connfd;</span><br><span class="line">    <span class="type">socklen_t</span> clilen;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">cliaddr</span>, <span class="title">servaddr</span>;</span></span><br><span class="line">    <span class="comment">// SOCKET</span></span><br><span class="line">    listenfd = socket(AF_INET, SO CK_STREAM, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    bzero(&amp;servaddr, <span class="keyword">sizeof</span>(servaddr));</span><br><span class="line">    servaddr.sin_family = AF_INET;</span><br><span class="line">    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</span><br><span class="line">    servaddr.sin_port = htons(SERV_PORT);</span><br><span class="line">    <span class="comment">// BIND</span></span><br><span class="line">    <span class="keyword">if</span> (bind(listenfd, (<span class="keyword">struct</span> sockaddr *) &amp;servaddr, <span class="keyword">sizeof</span>(servaddr)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        fatal(<span class="string">&quot;read was interrupted by a signal\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// LISTEN</span></span><br><span class="line">    listen(listenfd, LISTENQ);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        clilen = <span class="keyword">sizeof</span>(cliaddr);</span><br><span class="line">        <span class="comment">// ACCEPT</span></span><br><span class="line">        connfd = accept(listenfd, (<span class="keyword">struct</span> sockaddr *) &amp;cliaddr, &amp;clilen);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (fork() == <span class="number">0</span>) &#123;    <span class="comment">/* child process */</span></span><br><span class="line">            close(listenfd);    <span class="comment">/* close listening socket */</span></span><br><span class="line">            str_echo(connfd);    <span class="comment">/* process the request */</span></span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        close(connfd);            <span class="comment">/* parent closes connected socket */</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="select"><a href="#select" class="headerlink" title="select"></a>select</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/select.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/time.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">select</span><span class="params">(<span class="type">int</span> maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, <span class="type">const</span> <span class="keyword">struct</span> timeval *timeout)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// return :</span></span><br><span class="line"><span class="comment">// 若有就绪描述符则为其数目，若超时则为0，若出错则为-1</span></span><br></pre></td></tr></table></figure>



<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/11/18</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>    <span class="string">&quot;../lib/unp.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;server start...\n&quot;</span>);</span><br><span class="line">    <span class="type">int</span> i, maxi, maxfd, listenfd, connfd, sockfd;</span><br><span class="line">    <span class="type">int</span> nready, client[FD_SETSIZE];</span><br><span class="line">    <span class="type">ssize_t</span> n;</span><br><span class="line">    <span class="comment">// allset 维护当前活跃的客户端socket的读描述符集</span></span><br><span class="line">    fd_set rset, allset;</span><br><span class="line">    <span class="type">char</span> buf[MAXLINE];</span><br><span class="line">    <span class="type">socklen_t</span> clilen;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">cliaddr</span>, <span class="title">servaddr</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// SOCKET</span></span><br><span class="line">    listenfd = socket(AF_INET, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    bzero(&amp;servaddr, <span class="keyword">sizeof</span>(servaddr));</span><br><span class="line">    servaddr.sin_family = AF_INET;</span><br><span class="line">    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</span><br><span class="line">    servaddr.sin_port = htons(SERV_PORT);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// BIND</span></span><br><span class="line">    <span class="keyword">if</span> (bind(listenfd, (SA *) &amp;servaddr, <span class="keyword">sizeof</span>(servaddr)) &lt; <span class="number">0</span>) fatal(<span class="string">&quot;bind failure&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// LISTEN</span></span><br><span class="line">    listen(listenfd, LISTENQ);</span><br><span class="line"></span><br><span class="line">    maxfd = listenfd;              <span class="comment">/* initialize */</span></span><br><span class="line">    maxi = <span class="number">-1</span>;                     <span class="comment">/* index into client[] array */</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; FD_SETSIZE; i++) &#123;</span><br><span class="line">        client[i] = <span class="number">-1</span>;            <span class="comment">/* -1 indicates available entry */</span></span><br><span class="line">    &#125;</span><br><span class="line">    FD_ZERO(&amp;allset);</span><br><span class="line">    FD_SET(listenfd, &amp;allset); <span class="comment">// 单个监听描述符集</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        rset = allset;        <span class="comment">/* structure assignment */</span></span><br><span class="line">        nready = select(maxfd + <span class="number">1</span>, &amp;rset, <span class="literal">NULL</span>, <span class="literal">NULL</span>, <span class="literal">NULL</span>); <span class="comment">// 返回就绪的文件描述符的个数</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (FD_ISSET(listenfd, &amp;rset)) &#123;    <span class="comment">/* new client connection */</span></span><br><span class="line">            clilen = <span class="keyword">sizeof</span>(cliaddr);</span><br><span class="line">            <span class="comment">// ACCEPT</span></span><br><span class="line">            connfd = accept(listenfd, (SA *) &amp;cliaddr, &amp;clilen);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; FD_SETSIZE; i++)</span><br><span class="line">                <span class="keyword">if</span> (client[i] &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                    client[i] = connfd;    <span class="comment">/* save descriptor */</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">if</span> (i == FD_SETSIZE)</span><br><span class="line">                fatal(<span class="string">&quot;too many clients&quot;</span>);</span><br><span class="line"></span><br><span class="line">            FD_SET(connfd, &amp;allset);    <span class="comment">/* add new descriptor to set */</span></span><br><span class="line">            <span class="keyword">if</span> (connfd &gt; maxfd)</span><br><span class="line">                maxfd = connfd;            <span class="comment">/* for select */</span></span><br><span class="line">            <span class="keyword">if</span> (i &gt; maxi)</span><br><span class="line">                maxi = i;                <span class="comment">/* max index in client[] array */</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (--nready &lt;= <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;                <span class="comment">/* no more readable descriptors */</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt;= maxi; i++) &#123;    <span class="comment">/* check all clients for data */</span></span><br><span class="line">            <span class="keyword">if</span> ((sockfd = client[i]) &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span> (FD_ISSET(sockfd, &amp;rset)) &#123;</span><br><span class="line">                <span class="built_in">memset</span>(buf, <span class="number">0</span>, <span class="keyword">sizeof</span> buf);</span><br><span class="line">                <span class="keyword">if</span> ((n = read(sockfd, buf, MAXLINE)) == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">/*4connection closed by client */</span></span><br><span class="line">                    close(sockfd);</span><br><span class="line">                    <span class="comment">/* close this client connection */</span></span><br><span class="line">                    FD_CLR(sockfd, &amp;allset);</span><br><span class="line">                    client[i] = <span class="number">-1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="built_in">printf</span>(<span class="string">&quot;server receive is : &#x27;%s&#x27;\n&quot;</span>, buf);</span><br><span class="line">                    write(sockfd, buf, n);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (--nready &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;                <span class="comment">/* no more readable descriptors */</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="poll"><a href="#poll" class="headerlink" title="poll"></a>poll</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;poll.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">poll</span><span class="params">(<span class="keyword">struct</span> pollfd * fdarray, <span class="type">unsigned</span> <span class="type">long</span> nfds, <span class="type">int</span> timeout)</span>;</span><br><span class="line"><span class="comment">// return :</span></span><br><span class="line"><span class="comment">// 若有就绪描述符则为其数目，若超时则为0，若出错则为-1</span></span><br></pre></td></tr></table></figure>



<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/11/18</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>  <span class="string">&quot;../lib/unp.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>    <span class="string">&lt;limits.h&gt;</span>        <span class="comment">/* for OPEN_MAX */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;poll.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;server start...\n&quot;</span>);</span><br><span class="line">    <span class="type">int</span> i, maxi, listenfd, connfd, sockfd;</span><br><span class="line">    <span class="type">int</span> nready;</span><br><span class="line">    <span class="type">ssize_t</span> n;</span><br><span class="line">    <span class="type">char</span> buf[MAXLINE];</span><br><span class="line">    <span class="type">socklen_t</span> clilen;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> <span class="title">client</span>[<span class="title">OPEN_MAX</span>];</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">cliaddr</span>, <span class="title">servaddr</span>;</span></span><br><span class="line"></span><br><span class="line">    listenfd = socket(AF_INET, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    bzero(&amp;servaddr, <span class="keyword">sizeof</span>(servaddr));</span><br><span class="line">    servaddr.sin_family = AF_INET;</span><br><span class="line">    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);</span><br><span class="line">    servaddr.sin_port = htons(SERV_PORT);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (bind(listenfd, (SA *) &amp;servaddr, <span class="keyword">sizeof</span>(servaddr))) fatal(<span class="string">&quot;bind failure&quot;</span>);</span><br><span class="line"></span><br><span class="line">    listen(listenfd, LISTENQ);</span><br><span class="line"></span><br><span class="line">    client[<span class="number">0</span>].fd = listenfd; <span class="comment">// 监听套接字描述符</span></span><br><span class="line">    client[<span class="number">0</span>].events = POLLRDNORM;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; OPEN_MAX; i++)</span><br><span class="line">        client[i].fd = <span class="number">-1</span>;        <span class="comment">/* -1 indicates available entry */</span></span><br><span class="line">    maxi = <span class="number">0</span>;                    <span class="comment">/* max index into client[] array */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        nready = poll(client, maxi + <span class="number">1</span>, <span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (client[<span class="number">0</span>].revents &amp; POLLRDNORM) &#123;    <span class="comment">/* new client connection */</span></span><br><span class="line">            clilen = <span class="keyword">sizeof</span>(cliaddr);</span><br><span class="line">            connfd = accept(listenfd, (SA *) &amp;cliaddr, &amp;clilen);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; OPEN_MAX; i++)</span><br><span class="line">                <span class="keyword">if</span> (client[i].fd &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                    client[i].fd = connfd;    <span class="comment">/* save descriptor */</span></span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">if</span> (i == OPEN_MAX)</span><br><span class="line">                fatal(<span class="string">&quot;too many clients&quot;</span>);</span><br><span class="line"></span><br><span class="line">            client[i].events = POLLRDNORM;</span><br><span class="line">            <span class="keyword">if</span> (i &gt; maxi)</span><br><span class="line">                maxi = i;                <span class="comment">/* max index in client[] array */</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (--nready &lt;= <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;                <span class="comment">/* no more readable descriptors */</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt;= maxi; i++) &#123;    <span class="comment">/* check all clients for data */</span></span><br><span class="line">            <span class="keyword">if</span> ((sockfd = client[i].fd) &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span> (client[i].revents &amp; (POLLRDNORM | POLLERR)) &#123;</span><br><span class="line">                <span class="built_in">memset</span>(buf, <span class="number">0</span>, <span class="keyword">sizeof</span> buf);</span><br><span class="line">                <span class="keyword">if</span> ((n = read(sockfd, buf, MAXLINE)) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (errno == ECONNRESET) &#123;</span><br><span class="line">                        <span class="comment">/*4connection reset by client */</span></span><br><span class="line">                        <span class="built_in">printf</span>(<span class="string">&quot;client[%d] aborted connection\n&quot;</span>, i);</span><br><span class="line">                        close(sockfd);</span><br><span class="line">                        client[i].fd = <span class="number">-1</span>;</span><br><span class="line">                    &#125; <span class="keyword">else</span></span><br><span class="line">                        fatal(<span class="string">&quot;read error&quot;</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (n == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="comment">/*connection closed by client */</span></span><br><span class="line">                    <span class="built_in">printf</span>(<span class="string">&quot;client[%d] closed connection\n&quot;</span>, i);</span><br><span class="line">                    close(sockfd);</span><br><span class="line">                    client[i].fd = <span class="number">-1</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    write(sockfd, buf, n);</span><br><span class="line">                    <span class="built_in">printf</span>(<span class="string">&quot;server receive is : &#x27;%s&#x27;\n&quot;</span>, buf);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (--nready &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;                <span class="comment">/* no more readable descriptors */</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>





<h2 id="使用epoll"><a href="#使用epoll" class="headerlink" title="使用epoll"></a>使用epoll</h2><h2 id="Client"><a href="#Client" class="headerlink" title="Client"></a>Client</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @Author: 吉松阳</span></span><br><span class="line"><span class="comment"> * @Date: 2021/11/18</span></span><br><span class="line"><span class="comment"> * @Description: </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>    <span class="string">&quot;../lib/unp.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">str_cli</span><span class="params">(<span class="type">int</span> sockfd)</span> &#123;</span><br><span class="line">    <span class="type">char</span> sendline[MAXLINE] = &#123;<span class="number">0</span>&#125;, recvline[MAXLINE] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="keyword">while</span> (<span class="built_in">scanf</span>(<span class="string">&quot;%s&quot;</span>, sendline) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// SEND</span></span><br><span class="line">        write(sockfd, sendline, <span class="built_in">strlen</span>(sendline));</span><br><span class="line">        <span class="comment">// RECEIVE</span></span><br><span class="line">        <span class="keyword">if</span> (read(sockfd, recvline, MAXLINE) &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">            fatal(<span class="string">&quot;str_cli: server terminated prematurely&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;echoed message is %s\n&quot;</span>, recvline);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;client start\n&quot;</span>);</span><br><span class="line">    <span class="type">int</span> sockfd;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">servaddr</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="type">char</span> *server_ip = <span class="string">&quot;127.0.0.1&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// SOCKET</span></span><br><span class="line">    sockfd = socket(AF_INET, SOCK_STREAM, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    bzero(&amp;servaddr, <span class="keyword">sizeof</span>(servaddr));</span><br><span class="line">    servaddr.sin_family = AF_INET;</span><br><span class="line">    servaddr.sin_port = htons(SERV_PORT); <span class="comment">// </span></span><br><span class="line">    inet_pton(AF_INET, server_ip, &amp;servaddr.sin_addr);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// CONNECT</span></span><br><span class="line">    <span class="keyword">if</span> (connect(sockfd, (SA *) &amp;servaddr, <span class="keyword">sizeof</span>(servaddr)) &lt; <span class="number">0</span>) fatal(<span class="string">&quot;connection failed&quot;</span>);</span><br><span class="line"></span><br><span class="line">    str_cli(sockfd);        <span class="comment">/* do it all */</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>




<h2 id="Lib"><a href="#Lib" class="headerlink" title="Lib"></a>Lib</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* include unph */</span></span><br><span class="line"><span class="comment">/* Our own header.  Tabs are set for 4 spaces, not 8 */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span>	__unp_h</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	__unp_h</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/types.h&gt;</span>	<span class="comment">/* basic system data types */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/socket.h&gt;</span>	<span class="comment">/* basic socket definitions */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> TIME_WITH_SYS_TIME</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/time.h&gt;</span>	<span class="comment">/* timeval&#123;&#125; for select() */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;time.h&gt;</span>		<span class="comment">/* timespec&#123;&#125; for pselect() */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> HAVE_SYS_TIME_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/time.h&gt;</span>	<span class="comment">/* includes &lt;time.h&gt; unsafely */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;time.h&gt;</span>		<span class="comment">/* old system? */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;netinet/in.h&gt;</span>	<span class="comment">/* sockaddr_in&#123;&#125; and other Internet defns */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;arpa/inet.h&gt;</span>	<span class="comment">/* inet(3) functions */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;fcntl.h&gt;</span>		<span class="comment">/* for nonblocking */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;netdb.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/stat.h&gt;</span>	<span class="comment">/* for S_xxx file mode constants */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/uio.h&gt;</span>		<span class="comment">/* for iovec&#123;&#125; and readv/writev */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;sys/un.h&gt;</span>		<span class="comment">/* for Unix domain sockets */</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_SELECT_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/select.h&gt;</span>	<span class="comment">/* for convenience */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_SYSCTL_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_PARAM_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/param.h&gt;</span>	<span class="comment">/* OpenBSD prereq for sysctl.h */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/sysctl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_POLL_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;poll.h&gt;</span>		<span class="comment">/* for convenience */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_EVENT_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/event.h&gt;</span>	<span class="comment">/* for kqueue */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_STRINGS_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;strings.h&gt;</span>		<span class="comment">/* for convenience */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Three headers are normally needed for socket/file ioctl&#x27;s:</span></span><br><span class="line"><span class="comment"> * &lt;sys/ioctl.h&gt;, &lt;sys/filio.h&gt;, and &lt;sys/sockio.h&gt;.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_IOCTL_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/ioctl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_FILIO_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/filio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_SYS_SOCKIO_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;sys/sockio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span>	HAVE_PTHREAD_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> HAVE_NET_IF_DL_H</span></span><br><span class="line"><span class="meta"># <span class="keyword">include</span>	<span class="string">&lt;net/if_dl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> HAVE_NETINET_SCTP_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span>	<span class="string">&lt;netinet/sctp.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	LISTENQ		1024	<span class="comment">/* 2nd argument to listen() */</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Define some port number that can be used for our examples */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	SERV_PORT		 9877			<span class="comment">/* TCP and UDP */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	SERV_PORT_STR	<span class="string">&quot;9877&quot;</span>			<span class="comment">/* TCP and UDP */</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Miscellaneous constants */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	MAXLINE		4096	<span class="comment">/* max text line length */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	BUFFSIZE	8192	<span class="comment">/* buffer size for reads and writes */</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Following shortens all the typecasts of pointer arguments: */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span>	SA	struct sockaddr</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> fatal(message) fprintf(stderr, message)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span>	<span class="comment">/* __unp_h */</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/synchronized/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/synchronized/" class="post-title-link" itemprop="url">synchronized</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-16 22:34:01" itemprop="dateCreated datePublished" datetime="2022-03-16T22:34:01+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2023-01-05 01:07:48" itemprop="dateModified" datetime="2023-01-05T01:07:48+08:00">2023-01-05</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B/" itemprop="url" rel="index"><span itemprop="name">Java多线程</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="使用方式"><a href="#使用方式" class="headerlink" title="使用方式"></a>使用方式</h1><p>主要有3种使用方式:</p>
<p><strong>1.修饰实例方法：作用于当前实例加锁</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">		<span class="comment">// 代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>2.修饰静态方法：作用于当前类对象加锁</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">		<span class="comment">// 代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>3.修饰代码块：指定加锁对象，对给定对象加锁</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(<span class="built_in">this</span>)&#123;</span><br><span class="line">		<span class="comment">//代码                                  </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h1 id="底层实现"><a href="#底层实现" class="headerlink" title="底层实现"></a>底层实现</h1><p>synchronized的底层实现是完全依赖JVM虚拟机的,所以谈synchronized的底层实现，就不得不谈数据在JVM内存的存储：Java对象头，以及Monitor对象监视器。</p>
<h2 id="Java对象头"><a href="#Java对象头" class="headerlink" title="Java对象头"></a>Java对象头</h2><p>在JVM虚拟机中，对象在内存中的存储布局，可以分为三个区域:</p>
<ul>
<li>对象头(Header)</li>
<li>实例数据(Instance Data)</li>
<li>对齐填充(Padding)</li>
</ul>
<p><strong>Java对象头主要包括两部分数据：</strong></p>
<p><img src="/2022/03/16/synchronized/synchronized%E9%94%81%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png"></p>
<p><strong>1)类型指针（Klass Pointer）</strong></p>
<p>是对象指向它的类元数据的指针，虚拟机通过这个指针来确定这个对象是哪个类的实例；</p>
<p><strong>2)标记字段(Mark Word)</strong></p>
<p>用于存储对象自身的运行时数据，如哈希码（HashCode）、GC分代年龄、锁状态标志、持有锁的线程、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.</p>
<p>所以，很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。</p>
<p>64 位虚拟机 Mark Word 是 64bit 其结构如下：<br><img src="https://xiaomi-info.github.io/2020/03/24/synchronized/sync_1.png"></p>
<h2 id="Monitor"><a href="#Monitor" class="headerlink" title="Monitor"></a>Monitor</h2><p>monitor描述为对象监视器,可以类比为一个特殊的房间，这个房间中有一些被保护的数据，monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据，进入房间即为持有monitor，退出房间即为释放monitor。</p>
<p>使用syncrhoized加锁的同步代码块在字节码引擎中执行时，主要就是通过锁对象的monitor的取用(monitorenter)与释放(monitorexit)来实现的。（不过 syncrhoized 方法块不是使用到这两个字节码指令）</p>
<h3 id="线程状态流转在Monitor上的体现"><a href="#线程状态流转在Monitor上的体现" class="headerlink" title="线程状态流转在Monitor上的体现"></a>线程状态流转在Monitor上的体现</h3><p>当多个线程同时请求某个对象监视器时，对象监视器会设置几种状态用来区分请求的线程：</p>
<p>Contention List：所有请求锁的线程将被首先放置到该竞争队列<br>Entry List：Contention List中那些有资格成为候选人的线程被移到Entry List<br>Wait Set：那些调用wait方法被阻塞的线程被放置到Wait Set<br>OnDeck：任何时刻最多只能有一个线程正在竞争锁，该线程称为OnDeck<br>Owner：获得锁的线程称为Owner<br>!Owner：释放锁的线程</p>
<h1 id="锁升级"><a href="#锁升级" class="headerlink" title="锁升级"></a>锁升级</h1><p>在 JDK 6 中虚拟机团队对锁进行了重要改进，优化了其性能引入了 <strong>偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化</strong>等实现，其中<strong>锁消除和锁粗化</strong>本文不做详细讨论其余内容我们将对其进行逐一探究。</p>
<p>锁的4中状态：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态（级别从低到高）</p>
<h2 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h2><blockquote>
<p>为什么要引入偏向锁？</p>
</blockquote>
<p>因为经过HotSpot的作者大量的研究发现，<strong>大多数时候是不存在锁竞争的</strong>，<strong>常常是一个线程多次获得同一个锁</strong>，因此如果每次都要竞争锁会增大很多没有必要付出的代价，为了降低获取锁的开销，才引入的偏向锁。</p>
<h3 id="偏向锁的升级"><a href="#偏向锁的升级" class="headerlink" title="偏向锁的升级"></a>偏向锁的升级</h3><p><strong>当线程1访问代码块并获取锁对象时，会在java对象头和栈帧中记录偏向的锁的threadID</strong>，因为<strong>偏向锁不会主动释放锁</strong>，因此以后线程1再次获取锁的时候，需要<strong>比较当前线程的threadID和Java对象头中的threadID是否一致</strong>，如果一致（还是线程1获取锁对象），则无需使用CAS来加锁、解锁；如果不一致（其他线程，如线程2要竞争锁对象，而偏向锁不会主动释放因此还是存储的线程1的threadID），那么<strong>需要查看Java对象头中记录的线程1是否存活</strong>，</p>
<ol>
<li><p><strong>如果没有存活，那么锁对象被重置为无锁状态</strong>，其它线程（线程2）可以竞争将其设置为偏向锁cas；</p>
</li>
<li><p><strong>如果存活，那么立刻查找该线程（线程1）的栈帧信息</strong>，</p>
<ul>
<li>如果还是需要继续持有这个锁对象，那么<strong>暂停当前线程1，撤销偏向锁，升级为轻量级锁</strong>，</li>
<li>如果线程 1 不再使用该锁对象，那么<strong>将锁对象状态设为无锁状态，重新偏向新的线程</strong>cas。</li>
</ul>
</li>
</ol>
<blockquote>
<p>偏向锁不会主动释放，只有被动撤销。</p>
</blockquote>
<h3 id="偏向锁的取消"><a href="#偏向锁的取消" class="headerlink" title="偏向锁的取消"></a>偏向锁的取消</h3><p>偏向锁是默认开启的，而且开始时间一般是比应用程序启动慢几秒，如果不想有这个延迟，那么可以使用-XX:BiasedLockingStartUpDelay&#x3D;0；</p>
<p>如果不想要偏向锁，那么可以通过-XX:-UseBiasedLocking &#x3D; false来设置；</p>
<h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><p>只有一个线程执行同步块时进一步提高性能，适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。</p>
<h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><p>如果存在竞争会带来额外的锁撤销操作。</p>
<h2 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h2><blockquote>
<p>为什么要引入轻量级锁？</p>
</blockquote>
<p>轻量级锁考虑的是<strong>竞争锁对象的线程不多</strong>，而且<strong>线程持有锁的时间也不长</strong>的情景。</p>
<p>因为<strong>阻塞线程需要CPU从用户态转到内核态，代价较大</strong>，如果刚刚阻塞不久这个锁就被释放了，那这个代价就有点得不偿失了，因此这个时候就<strong>干脆不阻塞这个线程，让它自旋这等待锁释放</strong>。</p>
<p>轻量级锁什么时候升级为重量级锁？</p>
<p>线程1获取轻量级锁时会<strong>先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间（称为DisplacedMarkWord）</strong>，然后<strong>使用CAS把对象头中的内容替换为线程1存储的锁记录（DisplacedMarkWord）的地址</strong>；</p>
<p>如果在线程1复制对象头的同时（在线程1CAS之前），线程2也准备获取锁，复制了对象头到线程2的锁记录空间中，但是在<strong>线程2CAS的时候，发现线程1已经把对象头换了，线程2的CAS失败，那么线程2就尝试使用自旋锁来等待线程1释放锁</strong>。</p>
<h3 id="加锁"><a href="#加锁" class="headerlink" title="加锁"></a>加锁</h3><p><strong>多个线程竞争偏向锁导致偏向锁升级为轻量级锁。</strong></p>
<ol>
<li>JVM 在当前线程的栈帧中创建 Lock Reocrd，并将对象头中的 Mark Word 复制到 Lock Reocrd 中。（Displaced Mark Word）</li>
<li>线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁，如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧，如果是则说明已经获取锁，否则说明其它线程竞争锁则膨胀为重量级锁。</li>
</ol>
<h3 id="解锁"><a href="#解锁" class="headerlink" title="解锁"></a>解锁</h3><ol>
<li>使用 CAS 操作将 Mark Word 还原</li>
<li>如果第 1 步执行成功则释放完成</li>
<li>如果第 1 步执行失败则膨胀为重量级锁（执行失败说明存在竞争）。<del>存疑</del></li>
</ol>
<h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><p>其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下，可以避免重量级锁引起的性能消耗。</p>
<h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><p>在有多线程竞争的情况下轻量级锁增加了额外开销。</p>
<p>但是如果自旋的时间太长也不行，因为自旋是要消耗CPU的，因此自旋的次数是有限制的，比如10次或者100次，如果自旋次数到了线程1还没有释放锁，或者线程1还在执行，线程2还在自旋等待，这时又有一个线程3过来竞争这个锁对象，那么这个时候轻量级锁就会膨胀为重量级锁。这个时候又有了自适应自旋的概念。</p>
<h2 id="重量级锁"><a href="#重量级锁" class="headerlink" title="重量级锁"></a>重量级锁</h2><p><strong>重量级锁把除了拥有锁的线程都阻塞，防止CPU空转</strong>。</p>
<p>注意：为了避免无用的自旋，<strong>轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了</strong>；</p>
<p><strong>偏向锁升级为轻量级锁也不能再降级为偏向锁</strong>。</p>
<p>一句话就是锁可以升级不可以降级，但是偏向锁状态可以被重置为无锁状态。</p>
<blockquote>
<p>但是一旦释放重量级锁，就重新回到上面的流程中了。</p>
</blockquote>
<p>在重量级锁中没有竞争到锁的对象会 park 被挂起，退出同步块时 unpark 唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。</p>
<p><code>ObjectMonitor</code> 中包含一个同步队列（由 <code>_cxq</code> 和 <code>_EntryList</code> 组成）一个等待队列（ <code>_WaitSet</code> ）。</p>
<ul>
<li>被<code>notify</code>或 <code>notifyAll</code> 唤醒时根据 <code>policy</code> 策略选择加入的队列（policy 默认为 0）</li>
<li>退出同步块时根据 <code>QMode</code> 策略来唤醒下一个线程（QMode 默认为 0）</li>
</ul>
<p>这里稍微提及一下<strong>管程</strong>这个概念。synchronized 关键字及 <code>wait</code>、<code>notify</code>、<code>notifyAll</code> 这三个方法都是管程的组成部分。可以说管程就是一把解决并发问题的万能钥匙。有两大核心问题管程都是能够解决的：</p>
<ul>
<li><strong>互斥</strong>：即同一时刻只允许一个线程访问共享资源；</li>
<li><strong>同步</strong>：即线程之间如何通信、协作。</li>
</ul>
<p><code>synchronized</code> 的 <code>monitor</code>锁机制和 JDK 并发包中的 <code>AQS</code> 是很相似的，只不过 <code>AQS</code> 中是一个同步队列多个等待队列。熟悉 <code>AQS</code> 的同学可以拿来做个对比。</p>
<blockquote>
<p>在 HotSpot 中 monitor 是由 ObjectMonitor 实现的。</p>
</blockquote>
<h3 id="monitor-竞争过程"><a href="#monitor-竞争过程" class="headerlink" title="monitor 竞争过程"></a>monitor 竞争过程</h3><ol>
<li>通过 CAS 尝试把 monitor 的 owner 字段设置为当前线程。</li>
<li>如果设置之前的 owner 指向当前线程，说明当前线程再次进入 monitor，即重入锁执行 recursions ++ , 记录重入的次数。</li>
<li>如果当前线程是第一次进入该 monitor, 设置 recursions 为 1,_owner 为当前线程，该线程成功获得锁并返回。</li>
<li>如果获取锁失败，则等待锁的释放。</li>
</ol>
<p>执行 <code>monitorenter</code> 指令时会调用cpp编写的代码。</p>
<h3 id="monitor-释放"><a href="#monitor-释放" class="headerlink" title="monitor 释放"></a>monitor 释放</h3><p>当某个持有锁的线程执行完同步代码块时，会释放锁并 <code>unpark</code> 后续线程（由于篇幅只保留重要代码）。</p>
<blockquote>
<p>参考<br><a target="_blank" rel="noopener" href="https://blog.csdn.net/tongdanping/article/details/79647337">https://blog.csdn.net/tongdanping/article/details/79647337</a><br><a target="_blank" rel="noopener" href="https://juejin.cn/post/6844903913120874510">https://juejin.cn/post/6844903913120874510</a><br><a target="_blank" rel="noopener" href="https://juejin.cn/post/6936067917255540767">https://juejin.cn/post/6936067917255540767</a><br><a target="_blank" rel="noopener" href="https://xiaomi-info.github.io/2020/03/24/synchronized/">https://xiaomi-info.github.io/2020/03/24/synchronized/</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/LVS/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/LVS/" class="post-title-link" itemprop="url">LVS</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>
      

      <time title="创建时间：2022-03-16 16:43:56 / 修改时间：16:56:18" itemprop="dateCreated datePublished" datetime="2022-03-16T16:43:56+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">分布式</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="LVS的调度算法简介"><a href="#LVS的调度算法简介" class="headerlink" title="LVS的调度算法简介"></a>LVS的调度算法简介</h1><ol>
<li><p><strong>轮叫调度（Round Robin）</strong><br>调度器通过“轮叫”调度算法<strong>将外部请求按顺序轮流分配到集群中的真实服务器上</strong>，它均等地对待每一台服务器，而不管服务器上实际的连接数和系统负载。大锅饭调度：rr - 纯轮询方式，比较垃圾。把每项请求按顺序在真正服务器中分派</p>
</li>
<li><p><strong>加权轮叫（Weighted Round Robin）</strong><br>调度器通过“加权轮叫”调度算法<strong>根据真实服务器的不同处理能力来调度访问请求</strong>。这样可以保证处理能力强的服务器能处理更多的访问流量。调度器可以自动问询真实服务器的负载情况，并动态地调整其权值。<br>带权重的大锅饭调度：wrr -带权重轮询方式。把每项请求按顺序在真正服务器中循环分派，但是给能力较大的服务器分派较多的作业。</p>
</li>
<li><p><strong>最少链接（Least Connections）</strong><br>调度器通过“最少连接”调度算法动态地<strong>将网络请求调度到已建立的链接数最少的服务器上</strong>。如果集群系统的真实服务器具有相近的系统性能，采用“最小连接”调度算法可以较好地均衡负载。<br>谁不干活就给谁分配：lc - 根据最小连接数分派</p>
</li>
<li><p><strong>加权最少链接（Weighted Least Connections）</strong><br>在集群系统中的服务器性能差异较大的情况下，调度器采用“加权最少链接”调度算法优化负载均衡性能，具有较高权值的服务器将承受较大比例的活动连接负载。调度器可以自动问询真实服务器的负载情况，并动态地调整其权值。<br>带权重的谁不干活就给谁分配：wlc - 带权重的。机器配置好的权重高</p>
</li>
<li><p>基于局部性的最少链接（Locality-Based Least Connections）<br>“基于局部性的最少链接”调度算法是针对目标IP地址的负载均衡，目前<strong>主要用于Cache集群系统</strong>。该算法根据请求的目标IP地址找出该目标IP地址最近使用的服务器，若该服务器是可用的且没有超载，将请求发送到该服务器；若服务器不存在，或者该服务器超载且有服务器处于一半的工作负载，则用“最少链接” 的原则选出一个可用的服务器，将请求发送到该服务器。<br>基于地区的最少连接调度：lblc - 缓存服务器集群。基于本地的最小连接。把请求传递到负载小的服务器上</p>
</li>
<li><p>带复制的基于局部性最少链接（Locality-Based Least Connections with Replication）<br>“带复制的基于局部性最少链接”调度算法也是针对目标IP地址的负载均衡，目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标 IP地址到一组服务器的映射，而LBLC算法维护从一个目标IP地址到一台服务器的映射。该算法根据请求的目标IP地址找出该目标IP地址对应的服务器组，按“最小连接”原则从服务器组中选出一台服务器，若服务器没有超载，将请求发送到该服务器；若服务器超载，则按“最小连接”原则从这个集群中选出一台服务器，将该服务器加入到服务器组中，将请求发送到该服务器。同时，当该服务器组有一段时间没有被修改，将最忙的服务器从服务器组中删除，以降低复制的程度。<br>带有复制调度的基于地区的最少连接调度：lblcr - 带复制调度的缓存服务器集群。某页面缓存在服务器A上，被访问次数极高，而其他缓存服务器负载较低，监视是否访问同一页面，如果是访问同一页面则把请求分到其他服务器。</p>
</li>
<li><p>目标地址散列（Destination Hashing）<br>“目标地址散列”调度算法<strong>根据请求的目标IP地址，作为散列键（Hash Key）从静态分配的散列表找出对应的服务器</strong>，若该服务器是可用的且未超载，将请求发送到该服务器，否则返回空。<br>目标散列调度：realserver中绑定两个ip。ld判断来者的ISP商，将其转到相应的IP。</p>
</li>
<li><p>源地址散列（Source Hashing）<br>“源地址散列”调度算法根据请求的源IP地址，作为散列键（Hash Key）从静态分配的散列表找出对应的服务器，若该服务器是可用的且未超载，将请求发送到该服务器，否则返回空。了解这些算法原理能够在特定的应用场合选择最适合的调度算法，从而尽可能地保持Real Server的最佳利用性。当然也可以自行开发算法，不过这已超出本文范围，请参考有关算法原理的资料。<br>源散列调度：源地址散列。基于client地址的来源区分。（用的很少）</p>
</li>
<li><p>最短的期望的延迟（Shortest Expected Delay Scheduling SED）<br>基于wlc算法。这个必须举例来说了ABC三台机器分别权重123 ，连接数也分别是123。那么如果使用WLC算法的话一个新请求进入时它可能会分给ABC中的任意一个。使用sed算法后会进行这样一个运算A:（1+1)&#x2F;1B:（1+2)&#x2F;2C:（1+3)&#x2F;3根据运算结果，把连接交给C 。</p>
</li>
<li><p>最少队列调度（Never Queue Scheduling NQ）<br>无需队列。如果有台realserver的连接数＝0就直接分配过去，不需要在进行sed运算。</p>
</li>
</ol>
<blockquote>
<p>参考文章<br><a target="_blank" rel="noopener" href="http://zh.linuxvirtualserver.org/node/2903">http://zh.linuxvirtualserver.org/node/2903</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/lcl_xiaowugui/article/details/81701949">https://blog.csdn.net/lcl_xiaowugui/article/details/81701949</a></p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/lzppcc/articles/3072657.html">https://www.cnblogs.com/lzppcc/articles/3072657.html</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.51cto.com/blief/1745134">https://blog.51cto.com/blief/1745134</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/" class="post-title-link" itemprop="url">分布式事务</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-16 16:29:42" itemprop="dateCreated datePublished" datetime="2022-03-16T16:29:42+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-10-17 14:26:51" itemprop="dateModified" datetime="2022-10-17T14:26:51+08:00">2022-10-17</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">分布式</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="CAP"><a href="#CAP" class="headerlink" title="CAP"></a>CAP</h1><p>CAP定理：一个分布式系统不可能满足<strong>一致性</strong>，<strong>可用性</strong>和<strong>分区容错性</strong>这三个需求，最多只能同时满足两个。</p>
<ul>
<li><p>C: Consistency 一致性</p>
<p>一致性：在分布式系统完成某写操作后任何读操作，都应该获取到该写操作写入的那个最新的值。相当于要求分布式系统中的各节点时时刻刻保持数据的一致性。</p>
</li>
<li><p>A: Availability 可用性</p>
<p>可用性： 一直可以正常的做读写操作。简单而言就是客户端一直可以正常访问并得到系统的正常响应。用户角度来看就是不会出现系统操作失败或者访问超时等问题。</p>
</li>
<li><p>P: Tolerance of network Partition 分区容忍性</p>
<p>分区容错性：指的分布式系统中的某个节点或者网络分区出现了故障的时候，整个系统仍然能对外提供满足一致性和可用性的服务，也就是说部分故障不影响整体使用。</p>
</li>
</ul>
<h2 id="CP的实现"><a href="#CP的实现" class="headerlink" title="CP的实现"></a>CP的实现</h2><p><img src="/2022/03/16/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/cap-c.png" alt="cap-c"></p>
<h2 id="AP的实现"><a href="#AP的实现" class="headerlink" title="AP的实现"></a>AP的实现</h2><p><img src="/2022/03/16/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/cap-a.png" alt="cap-a"></p>
<p>除非两个节点可以互相通信，才能既保证C又保证A，这又会导致丧失P性质。<br>一般来说，跨区域的系统，无法舍弃P性质，那么就只能在数据一致性和可用性上做一个艰难选择。</p>
<p>CAP三者不可兼得，该如何取舍？</p>
<ul>
<li><p>CA: 优先保证一致性和可用性，放弃分区容错。 这也意味着放弃系统的扩展性，系统不再是分布式的，有违设计的初衷。</p>
<p>如关系型数据库。</p>
</li>
<li><p>CP: 优先保证一致性和分区容错性，放弃可用性。在数据一致性要求比较高的场合(譬如:zookeeper，Hbase) 是比较常见的做法，一旦发生网络故障或者消息丢失，就会牺牲用户体验，等恢复之后用户才逐渐能访问。</p>
<p>如一些分布式数据库的实现。</p>
</li>
<li><p>AP: 优先保证可用性和分区容错性，放弃一致性。放弃一致性不是说一致性就不保证了，而是逐渐的变得一致。</p>
<p>如BASE的要求。</p>
</li>
</ul>
<h1 id="BASE"><a href="#BASE" class="headerlink" title="BASE"></a>BASE</h1><ul>
<li>Basically Availble –基本可用</li>
<li>Soft-state –软状态&#x2F;柔性事务</li>
<li>Eventual Consistency –最终一致性</li>
</ul>
<h2 id="Basically-Availble（基本可用）"><a href="#Basically-Availble（基本可用）" class="headerlink" title="Basically Availble（基本可用）"></a>Basically Availble（基本可用）</h2><p>基本可用是指分布式系统在出现不可预知故障的时候，允许损失部分可用性——但请注意，这绝不等价于系统不可用。</p>
<ul>
<li><strong>响应时间上的损失</strong>：正常情况下，一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果，但由于出现异常（比如系统部分机房发生断电或断网故障），查询结果的响应时间增加到了1~2秒</li>
<li><strong>功能上的损失</strong>：正常情况下，在一个电子商务网站上进行购物，消费者几乎能够顺利地完成每一笔订单，但是在一些节日大促购物高峰的时候，由于消费者的购物行为激增，为了保护购物系统的稳定性，<strong>部分消费者可能会被引导到一个降级页面</strong>。</li>
</ul>
<h2 id="Soft-state（软状态-x2F-柔性事务）"><a href="#Soft-state（软状态-x2F-柔性事务）" class="headerlink" title="Soft-state（软状态&#x2F;柔性事务）"></a>Soft-state（软状态&#x2F;柔性事务）</h2><p>软状态，和硬状态相对，是指<strong>允许系统中的数据存在中间状态</strong>，并认为该中间状态的存在不会影响系统的整体可用性，<strong>即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时</strong>。</p>
<h2 id="Eventual-Consistency（最终一致性）"><a href="#Eventual-Consistency（最终一致性）" class="headerlink" title="Eventual Consistency（最终一致性）"></a>Eventual Consistency（最终一致性）</h2><p>最终一致性强调的是系统中所有的数据副本，在经过一段时间的同步后，最终能够达到一个一致的状态。</p>
<p>因此，最终一致性的本质是需要系统保证最终数据能够达到一致，而不需要实时保证系统数据的强一致性。</p>
<p>最终一致性是一种特殊的弱一致性：系统能够保证在没有其他新的更新操作的情况下，数据最终一定能够达到一致的状态，因此所有客户端对系统的数据访问都能够获取到最新的值。</p>
<p>在没有发生故障的前提下，数据达到一致状态的时间延迟，取决于网络延迟，系统负载和数据复制方案设计等因素</p>
<h1 id="NWR模型"><a href="#NWR模型" class="headerlink" title="NWR模型"></a>NWR模型</h1><blockquote>
<p>NWR模型试图解决多副本的数据一致性问题。</p>
</blockquote>
<ul>
<li>N: 复制的节点数量</li>
<li>R: 成功读操作的最小节点数</li>
<li>W: 成功写操作的最小节点数</li>
</ul>
<p>只需W + R &gt; N，就可以保证强一致性，<strong>因为读取数据的节点和被同步写入的节点是有重叠的</strong>。</p>
<p>例如：<br>N&#x3D;3，W&#x3D;2, R&#x3D;2 ，那么表示系统中数据有3个不同的副本，<br>当进行写操作时，需要等待至少有2个副本完成了该写操作系统才会返回执行成功的状态；<br>对于读操作，系统有同样的特性。<br>由于R+ W&gt;N,因此该系统是可以保证强一致性的。</p>
<p>R+ W &gt; N时，读(写)延迟由最慢的R(W)副本决定，有时为了获得较高的性能和较小的延迟，R和w的和可能小于N,这时系统不能保证读操作能获取最新的数据。</p>
<p>配置的几种特殊情况</p>
<ul>
<li>W &#x3D; 1， R &#x3D; N ，对写操作要求高性能高可用</li>
<li>R &#x3D; 1 ， W &#x3D; N ，对读操作要求高性能高可用，比如类似cache之类业务</li>
<li>W &#x3D; Q, R &#x3D; Q where Q &#x3D; N &#x2F; 2 + 1 一般应用适用，读写性能之间取得平衡，如N&#x3D;3 ， W&#x3D;2 ， R&#x3D;2。</li>
</ul>
<h1 id="2PC-XA协议"><a href="#2PC-XA协议" class="headerlink" title="2PC-XA协议"></a>2PC-XA协议</h1><blockquote>
<p>2PC的传统方案是在数据库层面实现的，如Oracle、MySQL都支持2PC协议，为了统一标准减少行业内不必要的对接成本，需要制定标准化的处理模型及接口标准，国际开放标准组织Open Group定义分布式事务处理模型<strong>DTP</strong>（Distributed Transaction Processing Reference Model）。</p>
<p>DTP模型定义TM和RM之间通讯的接口规范叫<strong>XA</strong>，简单理解为数据库提供的2PC接口协议，<strong>基于数据库的XA协议来实现2PC又称为XA方案</strong>。</p>
</blockquote>
<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>XA 分布式协议主要有两个角色：</p>
<ul>
<li>事务管理器（Transaction Manager 协调者）<br>事务管理器作为全局事务的协调管理者，与每个资源管理器通信，完成分布式事务的管理。</li>
<li>资源管理器 （Resource Manager 参与者）<br>资源管理器管理每个参与者的事务资源，其应该具有提交和回滚的能力，如数据库。</li>
</ul>
<blockquote>
<p>XA事务由一个或多个资源管理器（Resource Managers）、一个事务管理器（Transaction Manager）以及一个应用程序（Application Program）组成。</p>
<ul>
<li>资源管理器（RM）：参与者。提供访问事务资源的方法。通常一个<a target="_blank" rel="noopener" href="https://cloud.tencent.com/solution/database?from=10680">数据库</a>就是一个资源管理器。</li>
<li>事务管理器（TM）：协调者。分配标识符，监视事务的进度，并负责事务完成和故障恢复。</li>
<li>应用程序（AP）：发起者。定义事务的边界，制定全局事务中的操作。</li>
</ul>
</blockquote>
<h2 id="算法流程"><a href="#算法流程" class="headerlink" title="算法流程"></a>算法流程</h2><p>XA 分布式协议制定的分段提交过程：</p>
<p><img src="https://ask.qcloudimg.com/http-save/yehe-4752702/eetmyegzko.png?imageView2/2/w/1620"></p>
<ul>
<li><p>第一阶段（ prepare ）<br>第一阶段<strong>每个参与者准备执行事务并对需要的资源加锁</strong>，<strong>进入 ready 状态，并通知协调者已经准备就绪</strong>。</p>
</li>
<li><p>第二阶段（ commit ）<br>第二阶段当协调者确认<strong>每个参与者都 ready 后，通知参与者进行 commit 操作</strong>；</p>
<p>如果<strong>有参与者 fail ，则发送 rollback 命令，各参与者作回滚。</strong></p>
</li>
</ul>
<blockquote>
<ul>
<li>投票阶段<ul>
<li>在请求阶段，协调者将通知事务参与者准备提交或取消事务，然后进入表决过程</li>
<li>在表决过程中，参与者将告知协调者自己的决策：同意（事务参与者本地作业执行成功）或取消（本地作业执行故障）</li>
</ul>
</li>
<li>提交阶段<ul>
<li>协调者将基于第一个阶段的投票结果进行决策：提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务，否则协调者将通知所有的参与者取消事务</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://www.jianshu.com/p/7003d58ea182">MySQL XA 介绍</a></p>
<p>MySQL XA 的命令集合如下：</p>
<ol>
<li>XA START xid: 开启一个事务，并将事务置于ACTIVE状态，此后执行的SQL语句都将置于该是事务中。</li>
<li>XA END xid: 将事务置于IDLE状态，表示事务内的SQL操作完成。</li>
<li>XA PREPARE xid: 实现事务提交的准备工作，事务状态置于PREPARED状态。事务如果无法完成提交前的准备操作，该语句会执行失败。</li>
<li>XA COMMIT xid: 事务最终提交，完成持久化。</li>
<li>XA ROLLBACK xid: 事务回滚终止。</li>
<li>XA RECOVER: 查看MySQL中存在的PREPARED状态的xa事务。</li>
</ol>
<p><img src="https://upload-images.jianshu.io/upload_images/7425654-e3e679d3ee065c4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1122/format/webp"></p>
</blockquote>
<h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><p>基于以上，可以很容易理解 2PC 的执行过程，同时我们也注意到它存在的缺点：</p>
<blockquote>
<ol>
<li>对高并发不友好。<br>在分布式事务的执行过程中，存在多次通信，占用时间长，并且<strong>在这个过程中所有节点处于阻塞状态</strong>，<strong>每个参与者持有的资源始终要加锁</strong>。</li>
<li>单点故障。由上面可知协调者扮演着非常重要的角色，一旦协调者发生故障，参与者就会一直阻塞下去。尤其在第二阶段，协调者发生故障，那么所有的参与者还都处于锁定事务资源的状态中，而无法继续完成事务操作。</li>
<li>数据不一致。在第二阶段中，当协调者向参与者发送 commit 请求之后，发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障，就会导致<strong>只有一部分参与者接受到了commit 请求</strong>。而在这部分参与者接到 commit 请求之后就会执行 commit 操作，但是其他未接到 commit 请求的机器则无法执行事务提交，就导致了数据的不一致。</li>
<li>二阶段无法解决的问题<br>协调者再发出commit消息之后宕机，而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者，这条事务的状态也是不确定的，没人知道事务是否被已经提交。</li>
</ol>
</blockquote>
<h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><blockquote>
<p>某班要组织一个同学聚会，前提条件是所有参与者同意则活动举行，任意一人拒绝则活动取消。</p>
</blockquote>
<p><strong>用2PC算法来执行过程如下</strong></p>
<ul>
<li><p>Phase 1Prepare: </p>
<ul>
<li>组织者(coordinator)打电话给所有参与者(participant) ，同时告知参与者列表</li>
<li>Proposal: 提出周六2pm-5pm举办活动</li>
<li>Vote: participant需vote结果给coordinator：accept or reject</li>
<li>Block: 如果accept, participant锁住周六2pm-5pm的时间，不再接受其他请求（这一步加锁很需要关注）。</li>
</ul>
</li>
<li><p>Phase 2 Commit or Abort</p>
<ul>
<li>如果所有参与者都同意，组织者coodinator通知所有参与者commit, 否则通知abort，participant解除锁定。</li>
</ul>
</li>
</ul>
<p><strong>Failure 典型失败情况分析</strong></p>
<ul>
<li>Participant failure: 任一参与者无响应，coordinator直接执行abort</li>
<li>Coordinator failure:<ol>
<li>Takeover: 如果participant一段时间没收到cooridnator确认(commit&#x2F;abort)，则认为coordinator不在了。这时候可自动成为Coordinator的备份(watchdog)</li>
<li>Query: watchdog根据phase 1接收的participant列表发起query</li>
<li>Vote: 所有participant回复vote结果给watchdog, accept or reject</li>
<li>Commit: 如果所有都同意，则commit, 否则abort</li>
</ol>
</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/270213092">XA与TCC的区别</a></p>
<p><a target="_blank" rel="noopener" href="https://juejin.cn/post/6844903951477768205">TCC与两阶段分布式事务处理的区别</a></p>
<h1 id="TCC（Try-Confirm-Cancel）"><a href="#TCC（Try-Confirm-Cancel）" class="headerlink" title="TCC（Try-Confirm-Cancel）"></a>TCC（Try-Confirm-Cancel）</h1><blockquote>
<p><strong>XA 协议在架构上与 TCC 模型相比，最大的不同是 XA 直接作用于资源层，而后者作用于服务层。</strong></p>
<p><strong>2PC是偏数据库层面的，而TCC是纯业务层面。</strong></p>
<p>TCC 事务机制相比于上面介绍的XA 事务机制，有以下优点： 性能提升：具体业务来实现控制资源锁的粒度变小，不会锁定整个资源。</p>
</blockquote>
<p>TCC是一种比较成熟的分布式事务解决方案，可用于解决跨库操作的数据一致性问题；<br>TCC是服务化的两阶段编程模型，其Try、Confirm、Cancel 3个方法均由业务编码实现；<br>其中Try操作作为一阶段，负责资源的检查和预留，Confirm操作作为二阶段提交操作，执行真正的业务，Cancel是预留资源的取消；</p>
<p>TCC 是服务化的二阶段编程模型，采用的补偿机制。（所谓 TCC 模式，是指支持把 <strong>自定义</strong> 的分支事务纳入到全局事务的管理中）</p>
<p>关于 TCC（Try-Confirm-Cancel）的概念，最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。<br>TCC 事务机制相比于上面介绍的 XA，解决了其几个缺点：</p>
<ol>
<li>解决了协调者单点，由主业务方发起并完成这个业务活动。业务活动管理器也变成多点，引入集群。</li>
<li>同步阻塞：引入超时，超时后进行补偿，并且不会锁定整个资源，将资源转换为业务逻辑形式，粒度变小。</li>
<li>数据一致性，有了补偿机制之后，由业务活动管理器控制一致性</li>
</ol>
<h2 id="前提"><a href="#前提" class="headerlink" title="前提"></a>前提</h2><ol>
<li>需要实现确认和补偿逻辑</li>
<li>需要支持幂等</li>
<li>无需本地数据库事务的支持（所以说它是业务层的）</li>
</ol>
<h2 id="工作机制"><a href="#工作机制" class="headerlink" title="工作机制"></a>工作机制</h2><h3 id="Try-阶段"><a href="#Try-阶段" class="headerlink" title="Try 阶段"></a>Try 阶段</h3><p>try 阶段主要是对业务系统做检测及资源预留。<br>这个阶段主要完成：</p>
<p>完成所有业务检查( 一致性 ) ;</p>
<p>预留必须业务资源( 准隔离性 ) ;</p>
<p>Try 尝试执行业务。</p>
<h3 id="Confirm-阶段"><a href="#Confirm-阶段" class="headerlink" title="Confirm 阶段"></a>Confirm 阶段</h3><p>确认执行真正执行业务，不作任何业务检查，只使用 Try 阶段预留的业务资源，Confirm 操作满足幂等性。要求具备幂等设计，Confirm 失败后需要进行重试。</p>
<h3 id="Cancel-阶段"><a href="#Cancel-阶段" class="headerlink" title="Cancel 阶段"></a>Cancel 阶段</h3><p>取消执行，释放 Try 阶段预留的业务资源 Cancel 操作满足幂等性 Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致。</p>
<h2 id="实现注意事项"><a href="#实现注意事项" class="headerlink" title="实现注意事项"></a>实现注意事项</h2><h3 id="1、业务操作分两阶段完成"><a href="#1、业务操作分两阶段完成" class="headerlink" title="1、业务操作分两阶段完成"></a>1、业务操作分两阶段完成</h3><p>如下图所示，接入TCC前，业务操作只需要一步就能完成，但是在接入TCC之后，需要考虑如何将其分成2阶段完成，把资源的检查和预留放在一阶段的Try操作中进行，把真正的业务操作的执行放在二阶段的Confirm操作中进行；</p>
<p><img src="https://yqfile.alicdn.com/1d374e0667107a41eea95f7457e87845733d4788.png"></p>
<p><strong>TCC服务要保证第一阶段Try操作成功之后，二阶段Confirm操作一定能成功。</strong></p>
<h3 id="2、允许空回滚"><a href="#2、允许空回滚" class="headerlink" title="2、允许空回滚"></a>2、允许空回滚</h3><p>如下图所示，事务协调器在调用TCC服务的一阶段Try操作时，可能会出现因为丢包而导致的网络超时，此时事务协调器会触发二阶段回滚，调用TCC服务的Cancel操作；</p>
<p>TCC服务在未收到Try请求的情况下收到Cancel请求，这种场景被称为空回滚；TCC服务在实现时应当允许空回滚的执行。（为什么要允许空回滚？分支事务有的confirm过，有的try执行过后可能都没有响应，这个时候全部分支事务都需要回滚，所以就要做到那些未confirm的分支事务的空回滚）</p>
<p><img src="https://yqfile.alicdn.com/d58efe4a70432198b57381e85e94018ae6999df7.png"></p>
<h3 id="3、防悬挂控制"><a href="#3、防悬挂控制" class="headerlink" title="3、防悬挂控制"></a>3、防悬挂控制</h3><p>如下图所示，事务协调器在调用TCC服务的一阶段Try操作时，可能会出现因网络拥堵而导致的超时，此时事务协调器会触发二阶段回滚，调用TCC服务的Cancel操作；在此之后，拥堵在网络上的一阶段Try数据包被TCC服务收到，出现了二阶段Cancel请求比一阶段Try请求先执行的情况；</p>
<p>用户在实现TCC服务时，应当允许空回滚，但是要拒绝执行空回滚之后到来的一阶段Try请求；</p>
<p><img src="https://yqfile.alicdn.com/591e0fa1ddabafd517d3e3e4a1c1e44c3ef2c8f8.png"></p>
<h3 id="4、幂等控制"><a href="#4、幂等控制" class="headerlink" title="4、幂等控制"></a>4、幂等控制</h3><p>无论是网络数据包重传，还是异常事务的补偿执行，都会导致TCC服务的Try、Confirm或者Cancel操作被重复执行；用户在实现TCC服务时，需要考虑幂等控制，即Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的；<br><img src="https://yqfile.alicdn.com/5a9b1eaa651a3bce1b8d4e47f38887bc9935b51d.png"></p>
<h3 id="5、业务数据可见性控制；"><a href="#5、业务数据可见性控制；" class="headerlink" title="5、业务数据可见性控制；"></a>5、业务数据可见性控制；</h3><p>TCC服务的一阶段Try操作会做资源的预留，在二阶段操作执行之前，如果其他事务需要读取被预留的资源数据，那么处于中间状态的业务数据该如何向用户展示，需要业务在实现时考虑清楚；通常的设计原则是“宁可不展示、少展示，也不多展示、错展示”；</p>
<h3 id="6、业务数据并发访问控制；"><a href="#6、业务数据并发访问控制；" class="headerlink" title="6、业务数据并发访问控制；"></a>6、业务数据并发访问控制；</h3><p>TCC服务的一阶段Try操作预留资源之后，在二阶段操作执行之前，预留的资源都不会被释放；如果此时其他分布式事务修改这些业务资源，会出现分布式事务的并发问题；</p>
<p>用户在实现TCC服务时，需要考虑业务数据的并发控制，尽量将逻辑锁粒度降到最低，以最大限度的提高分布式事务的并发性；</p>
<h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><ul>
<li>性能提升：具体业务来实现控制资源锁的粒度变小，不会锁定整个资源。</li>
<li>数据最终一致性：基于 Confirm 和 Cancel 的幂等性，保证事务最终完成确认或者取消，保证数据的一致性。</li>
<li>可靠性：解决了 XA 协议的协调者单点故障问题，由主业务方发起并控制整个业务活动，业务活动管理器也变成多点，引入集群。</li>
</ul>
<h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><p>TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现，业务耦合度较高，提高了开发成本。</p>
<blockquote>
<p>关于TCC的诸多落地的变种：<strong>异步确保型 TCC 解决方案</strong>、<strong>补偿型 TCC 解决方案</strong></p>
<p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/270213092">XA与TCC的区别——精读</a></p>
</blockquote>
<h1 id="Seata的AT模式"><a href="#Seata的AT模式" class="headerlink" title="Seata的AT模式"></a>Seata的AT模式</h1><h2 id="前提-1"><a href="#前提-1" class="headerlink" title="前提"></a>前提</h2><ul>
<li>基于支持本地 ACID 事务的关系型数据库。</li>
<li>Java 应用，通过 JDBC 访问数据库。</li>
</ul>
<h2 id="整体机制"><a href="#整体机制" class="headerlink" title="整体机制"></a>整体机制</h2><p>两阶段提交协议的演变：</p>
<ul>
<li>一阶段：<strong>业务数据和回滚日志记录在同一个本地事务中提交，释放本地锁和连接资源</strong>。</li>
<li>二阶段：<ul>
<li>提交异步化，非常快速地完成（如果是commit）。</li>
<li>回滚通过<strong>一阶段的回滚日志</strong>进行反向补偿（如果是rollback）。</li>
</ul>
</li>
</ul>
<h2 id="隔离性"><a href="#隔离性" class="headerlink" title="隔离性"></a>隔离性</h2><h3 id="写隔离"><a href="#写隔离" class="headerlink" title="写隔离"></a>写隔离</h3><ul>
<li><p>一阶段<strong>本地事务提交前</strong>，需要确保先拿到 <strong>全局锁</strong> 。</p>
</li>
<li><p>拿不到 <strong>全局锁</strong> ，不能提交本地事务。</p>
</li>
<li><p>拿 <strong>全局锁</strong> 的尝试被限制在一定范围内，超出范围将放弃，并回滚本地事务，释放本地锁。</p>
</li>
<li><p>如果tx1 二阶段全局提交，释放 <strong>全局锁</strong> 。tx2 拿到 <strong>全局锁</strong> 提交本地事务。</p>
</li>
<li><p>如果 tx1 的二阶段全局回滚，则 tx1 需要重新获取该数据的本地锁，进行反向补偿的更新操作，实现分支的回滚。此时，如果 tx2 仍在等待该数据的 <strong>全局锁</strong>，同时持有本地锁，则 tx1 的分支回滚会失败。分支的回滚会一直重试，直到 tx2 的 <strong>全局锁</strong> 等锁超时，放弃 <strong>全局锁</strong> 并回滚本地事务释放本地锁，tx1 的分支回滚最终成功。因为整个过程 <strong>全局锁</strong> 在 tx1 结束前一直是被 tx1 持有的，所以不会发生 <strong>脏写</strong> 的问题。</p>
</li>
</ul>
<h3 id="读隔离"><a href="#读隔离" class="headerlink" title="读隔离"></a>读隔离</h3><p>在数据库本地事务隔离级别 <strong>读已提交（Read Committed）</strong> 或以上的基础上，Seata（AT 模式）的默认全局隔离级别是 <strong>读未提交（Read Uncommitted）</strong> 。</p>
<p>如果应用在特定场景下，必需要求全局的 <strong>读已提交</strong> ，目前 Seata 的方式是通过 <strong>SELECT FOR UPDATE</strong> 语句的代理。</p>
<p>SELECT FOR UPDATE 语句的执行会申请 <strong>全局锁</strong> ，如果 <strong>全局锁</strong> 被其他事务持有，则释放本地锁（回滚 SELECT FOR UPDATE 语句的本地执行）并重试。这个过程中，查询是被 block 住的，直到 <strong>全局锁</strong> 拿到，即读取的相关数据是 <strong>已提交</strong> 的，才返回。</p>
<p>出于总体性能上的考虑，Seata 目前的方案并没有对所有 SELECT 语句都进行代理，仅针对 FOR UPDATE 的 SELECT 语句。</p>
<h2 id="工作机制-1"><a href="#工作机制-1" class="headerlink" title="工作机制"></a>工作机制</h2><h3 id="一阶段"><a href="#一阶段" class="headerlink" title="一阶段"></a>一阶段</h3><ol>
<li><strong>解析 SQL</strong>：得到 SQL 的类型（UPDATE），表（product），条件（where name &#x3D; ‘TXC’）等相关的信息。</li>
<li><strong>查询前镜像</strong>：根据解析得到的条件信息，生成查询语句，定位数据。</li>
<li><strong>执行业务 SQL</strong>：更新这条记录的 name 为 ‘GTS’。</li>
<li><strong>查询后镜像</strong>：根据前镜像的结果，通过 <strong>主键</strong> 定位数据。</li>
<li><strong>插入回滚日志</strong>：把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录，插入到 <code>UNDO_LOG</code> 表中。</li>
<li>提交前，<strong>向 TC 注册分支</strong>：<strong>申请记录的全局锁</strong> 。</li>
<li><strong>本地事务提交</strong>：<strong>业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交</strong>。</li>
<li><strong>将本地事务提交的结果上报给 TC</strong>。</li>
</ol>
<h3 id="二阶段"><a href="#二阶段" class="headerlink" title="二阶段"></a>二阶段</h3><h4 id="二阶段-回滚"><a href="#二阶段-回滚" class="headerlink" title="二阶段-回滚"></a>二阶段-回滚</h4><ol>
<li>收到 TC 的分支回滚请求，开启一个本地事务，执行如下操作。</li>
<li>通过 <strong>XID 和 Branch ID</strong> <strong>查找到相应的 UNDO LOG 记录</strong>。</li>
<li><strong>数据校验</strong>：拿 UNDO LOG 中的后镜与当前数据进行比较，如果有不同，说明数据被当前全局事务之外的动作做了修改。这种情况，需要根据配置策略来做处理，详细的说明在另外的文档中介绍。</li>
<li>根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息<strong>生成并执行回滚的语句</strong>；</li>
<li><strong>提交本地的补偿事务</strong>，并把本地事务的执行结果（即分支事务回滚的结果）上报给 TC。</li>
</ol>
<h4 id="二阶段-提交"><a href="#二阶段-提交" class="headerlink" title="二阶段-提交"></a>二阶段-提交</h4><ol>
<li>收到 TC 的分支提交请求，把请求放入一个异步任务的队列中，马上返回提交成功的结果给 TC。</li>
<li>异步任务阶段的分支提交请求将<strong>异步和批量地删除相应 UNDO LOG 记录</strong>。</li>
</ol>
<h1 id="消息队列的分布式事务解决方案"><a href="#消息队列的分布式事务解决方案" class="headerlink" title="消息队列的分布式事务解决方案"></a>消息队列的分布式事务解决方案</h1><h2 id="最大努力通知"><a href="#最大努力通知" class="headerlink" title="最大努力通知"></a>最大努力通知</h2><p><strong>最大努力通知是最简单的一种柔性事务</strong>，适用于一些最终一致性时间敏感度低的业务，且被动方处理结果 不影响主动方的处理结果。</p>
<p>这个方案的大致意思就是：</p>
<ol>
<li>系统 A 本地事务执行完之后，发送个消息到 MQ；</li>
<li>这里会有个专门消费 MQ 的服务，这个服务会消费 MQ 并调用系统 B 的接口；</li>
<li>要是系统 B 执行成功就 ok 了；要是系统 B 执行失败了，那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次，最后还是不行就放弃。</li>
</ol>
<h2 id="本地消息表"><a href="#本地消息表" class="headerlink" title="本地消息表"></a>本地消息表</h2><p><img src="https://xiaomi-info.github.io/2020/01/02/distributed-transaction/native-message.jpg"></p>
<ol>
<li>当系统 A 被其他系统调用发生数据库表更操作，首先会更新数据库的业务表，其次会往相同数据库的消息表中插入一条数据，两个操作发生在同一个事务中</li>
<li>系统 A 的脚本定期轮询本地消息往 mq 中写入一条消息，如果消息发送失败会进行重试</li>
<li>系统 B 消费 mq 中的消息，并处理业务逻辑。如果本地事务处理失败，会在继续消费 mq 中的消息进行重试，如果是<strong>业务上的失败</strong>，可以通知系统 A 进行回滚操作</li>
</ol>
<p>本地消息表实现的条件：</p>
<ol>
<li><strong>消费者与生成者的接口都要支持幂等</strong>；</li>
<li><strong>生产者需要额外的创建消息表</strong>；</li>
<li>需要提供<strong>补偿逻辑</strong>，<strong>如果消费者业务失败，需要生产者支持回滚操作</strong> ？；</li>
</ol>
<p>容错机制：</p>
<ol>
<li>步骤 1 失败时，事务直接回滚；</li>
<li>步骤 2、3 写 mq 与消费 mq 失败会进行重试；</li>
<li>步骤 3 业务失败系统 B 向<strong>系统 A 发起事务回滚操作</strong>？；</li>
</ol>
<p>此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。</p>
<p>消息日志可以存储到本地文本、数据库或消息队列，再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景，通过对账系统对事后问题的处理。</p>
<h2 id="可靠消息最终一致性"><a href="#可靠消息最终一致性" class="headerlink" title="可靠消息最终一致性"></a>可靠消息最终一致性</h2><p><img src="https://xiaomi-info.github.io/2020/01/02/distributed-transaction/mq-message.jpg"></p>
<ol>
<li>A 系统先向 mq 发送一条 prepare 消息，如果 prepare 消息发送失败，则直接取消操作</li>
<li>如果消息发送成功，则执行本地事务</li>
<li>如果本地事务执行成功，则想 mq 发送一条 confirm 消息，如果发送失败，则发送回滚消息</li>
<li>B 系统消费 mq 中的 confirm 消息，执行本地事务，并发送 ack 消息。如果 B 系统中的本地事务失败，会一直不断重试，如果是<strong>业务上的失败</strong>，会向 A 系统发起回滚请求</li>
<li><strong>mq 会定期轮询所有 prepared 消息调用系统 A 提供的接口查询消息的处理情况</strong>，如果该 prepare 消息本地事务处理成功，则重新发送 confirm 消息，否则直接回滚该消息</li>
</ol>
<p>该<strong>方案与本地消息最大的不同是去掉了本地消息表</strong>，其次本地消息表依赖消息表重试写入 mq 这一步由本方案中的轮询 prepare 消息状态来重试或者回滚该消息替代。其实现条件与容错方案基本一致。</p>
<p>目前市面上实现该方案的只有阿里的 RocketMq。</p>
<h2 id="DB与MQ的一致性如何保证？"><a href="#DB与MQ的一致性如何保证？" class="headerlink" title="DB与MQ的一致性如何保证？"></a>DB与MQ的一致性如何保证？</h2><blockquote>
<p>这里的DB不一定指支持ACID的关系型数据库，甚至不一定指数据库，而是指广义上的’数据源’。</p>
</blockquote>
<p>在实际场景中，业务系统对本地DB数据变更后会广播对应的消息，消费者消费消息做自己的业务逻辑，按正常逻辑消息会在数据库变更后发出，<strong>如果消息发送超时且失败那么DB和MQ之间就产生了不一致问题</strong>，如何解决呢？使用可靠消息来解决，核心逻辑保证消息从投递到消费的过程中不会丢失。</p>
<h3 id="本地消息表-生产者回调确认"><a href="#本地消息表-生产者回调确认" class="headerlink" title="本地消息表+生产者回调确认"></a>本地消息表+生产者回调确认</h3><p>(上面这张图对于本地消息表的实现还是有点不清楚，下面的这张图解释的更清楚一点)</p>
<p><img src="https://ask.qcloudimg.com/http-save/yehe-4752702/nihz7qk4ej.png?imageView2/2/w/1620"></p>
<p>工作机制：</p>
<ol>
<li>本地事务写业务DB的同时，写消息到消息表（一个事务中），初始消息状态标记为’未投递成功’。</li>
<li>本地事务执行成功后，向mq发送消息，消息发送成功回调中，将消息表中的消息状态标记为’投递成功’</li>
<li>本地线程定时轮训消息表中的’未投递成功’的消息，发送到mq，失败不断重试，确保一定到发送到mq。</li>
</ol>
<h3 id="RocketMQ的half-message"><a href="#RocketMQ的half-message" class="headerlink" title="RocketMQ的half-message"></a>RocketMQ的half-message</h3><p><a target="_blank" rel="noopener" href="https://juejin.cn/post/7051937718892232734">RocketMQ4.9.1源码分析_(事务) Half半消息处理</a></p>
<p>上面的方式需要业务系统维护消息状态，这部分可以交给中间件来实现，实现逻辑会变得不一样。</p>
<p>(也就是上面所讲的<strong>可靠消息最终一致性</strong>的那张图，目前市面上只有RocketMQ支持这种功能。阿里内部支持的实际是Notify)</p>
<p><img src="https://ask.qcloudimg.com/http-save/yehe-4752702/68emv4778q.png?imageView2/2/w/1620"></p>
<h1 id="对账"><a href="#对账" class="headerlink" title="对账"></a>对账</h1><p>这里简单的说出一种解决思路：</p>
<p>所有操作之前，发消息；消费消息端查三个分支事务是否满足原子性，不满足则报警或者自动订正。</p>
<h1 id="推荐文章"><a href="#推荐文章" class="headerlink" title="推荐文章"></a>推荐文章</h1><blockquote>
<p><a target="_blank" rel="noopener" href="https://xiaomi-info.github.io/2020/01/02/distributed-transaction/">小米-分布式事务，这一篇就够了</a></p>
<p><a target="_blank" rel="noopener" href="https://cloud.tencent.com/developer/article/1763155">分布式系统：数据一致性解决方案</a></p>
<p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/270213092">XA与TCC的区别</a></p>
<p><a target="_blank" rel="noopener" href="https://seata.io/zh-cn/docs/overview/what-is-seata.html">Seata 是什么?</a></p>
<p><a target="_blank" rel="noopener" href="https://houbb.github.io/2018/09/02/sql-distribute-transaction-mq">本地消息表 （经典的ebay模式）</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/Nginx%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5/" class="post-title-link" itemprop="url">Nginx负载均衡策略</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>
      

      <time title="创建时间：2022-03-16 16:19:46 / 修改时间：16:40:22" itemprop="dateCreated datePublished" datetime="2022-03-16T16:19:46+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Nginx/" itemprop="url" rel="index"><span itemprop="name">Nginx</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="负载均衡的5种策略"><a href="#负载均衡的5种策略" class="headerlink" title="负载均衡的5种策略"></a>负载均衡的5种策略</h1><p>要理解负载均衡，必须先搞清楚正向代理和反向代理。</p>
<p><strong>负载均衡的几种常用方式</strong></p>
<h2 id="1、轮询（默认）"><a href="#1、轮询（默认）" class="headerlink" title="1、轮询（默认）"></a>1、轮询（默认）</h2><p>每个请求按时间顺序逐一分配到不同的后端服务器，如果后端服务器down掉，能自动剔除。</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">upstream</span> backserver &#123;</span><br><span class="line">    <span class="attribute">server</span> <span class="number">192.168.0.14</span>;</span><br><span class="line">    <span class="attribute">server</span> <span class="number">192.168.0.15</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="2、weight"><a href="#2、weight" class="headerlink" title="2、weight"></a>2、weight</h2><p>指定轮询几率，weight和访问比率成正比，用于后端服务器性能不均的<br>情况。</p>
<figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">upstream backserver &#123;</span><br><span class="line">   <span class="built_in"> server </span>192.168.0.14 <span class="attribute">weight</span>=3;</span><br><span class="line">   <span class="built_in"> server </span>192.168.0.15 <span class="attribute">weight</span>=7;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>权重越高，在被访问的概率越大，如上例，分别是30%，70%。</p>
<h2 id="3、ip-hash"><a href="#3、ip-hash" class="headerlink" title="3、ip_hash"></a>3、ip_hash</h2><p>上述方式存在一个问题就是说，在负载均衡系统中，假如用户在某台服务器上登录了，那么该用户第二次请求的时候，因为我们是负载均衡系统，每次请求都会重新定位到服务器集群中的某一个，那么<strong>已经登录某一个服务器的用户再重新定位到另一个服务器，其登录信息将会丢失，这样显然是不妥的</strong>。<br>我们可以采用<strong>ip_hash</strong>指令解决这个问题，如果客户已经访问了某个服务器，当用户再次访问时，会将该请求通过<strong>哈希算法，自动定位到该服务器</strong>。<br>每个请求按访问ip的hash结果分配，这样每个访客固定访问一个后端服务器，可以解决<strong>session的问题</strong>。</p>
<figure class="highlight roboconf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">upstream backserver &#123;</span><br><span class="line">    <span class="attribute">ip_hash;</span></span><br><span class="line"><span class="attribute">    server 192.168.0.14</span>:88;</span><br><span class="line">    <span class="attribute">server 192.168.0.15</span>:80;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="4、fair（第三方）"><a href="#4、fair（第三方）" class="headerlink" title="4、fair（第三方）"></a>4、fair（第三方）</h2><p>按后端服务器的响应时间来分配请求，响应时间短的优先分配。</p>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">upstream backserver &#123;</span><br><span class="line">    server server1<span class="comment">;</span></span><br><span class="line">    server server2<span class="comment">;</span></span><br><span class="line">    fair<span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="5、url-hash（第三方）"><a href="#5、url-hash（第三方）" class="headerlink" title="5、url_hash（第三方）"></a>5、url_hash（第三方）</h2><p>按访问url的hash结果来分配请求，使每个url定向到同一个（对应的）后端服务器，后端服务器为缓存时比较有效。</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">upstream</span> backserver &#123;</span><br><span class="line">    <span class="attribute">server</span> squid1:<span class="number">3128</span>;</span><br><span class="line">    <span class="attribute">server</span> squid2:<span class="number">3128</span>;</span><br><span class="line">    <span class="attribute">hash</span> <span class="variable">$request_uri</span>;</span><br><span class="line">    <span class="attribute">hash_method</span> crc32;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h1 id="配置实践"><a href="#配置实践" class="headerlink" title="配置实践"></a>配置实践</h1><p>在需要使用负载均衡的server中增加</p>
<figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">proxy_pass http://backserver/; </span><br><span class="line">upstream backserver&#123; </span><br><span class="line">    ip_hash; </span><br><span class="line">    server <span class="number">127.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span>:<span class="number">9090</span> down; (down 表示单前的server暂时不参与负载) </span><br><span class="line">    server <span class="number">127.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span>:<span class="number">8080</span> weight=<span class="number">2</span>; (weight 默认为<span class="number">1</span>; weight越大，负载的权重就越大) </span><br><span class="line">    server <span class="number">127.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span>:<span class="number">6060</span>; </span><br><span class="line">    server <span class="number">127.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span>:<span class="number">7070</span> backup; (其它所有的非backup机器down或者忙的时候，请求backup机器) </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure>

<p>max_fails：允许请求失败的次数默认为1.当超过最大次数时，返回proxy_next_upstream 模块定义的错误;</p>
<p>fail_timeout: max_fails次失败后，暂停的时间;</p>
<p>配置实例：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">user  nobody;</span></span><br><span class="line"></span><br><span class="line">worker_processes  4;</span><br><span class="line">events &#123;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">最大并发数</span></span><br><span class="line">worker_connections  1024;</span><br><span class="line">&#125;</span><br><span class="line">http&#123;</span><br><span class="line">    # 待选服务器列表</span><br><span class="line">    upstream myproject&#123;</span><br><span class="line">        # ip_hash指令，将同一用户引入同一服务器。</span><br><span class="line">        ip_hash;</span><br><span class="line">        server 125.219.42.4 fail_timeout=60s;</span><br><span class="line">        server 172.31.2.183;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    server&#123;</span><br><span class="line">        # 监听端口</span><br><span class="line">        listen 80;</span><br><span class="line">        # 根目录下</span><br><span class="line">        location / &#123;</span><br><span class="line">        # 选择哪个服务器列表</span><br><span class="line">            proxy_pass http://myproject;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<blockquote>
<p>参考链接<br><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000014483200">nginx负载均衡的5种策略</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/16/CGLIB%E4%BB%A3%E7%90%86%E5%92%8CJDK%E4%BB%A3%E7%90%86/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/CGLIB%E4%BB%A3%E7%90%86%E5%92%8CJDK%E4%BB%A3%E7%90%86/" class="post-title-link" itemprop="url">CGLIB代理和JDK代理</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-16 00:00:00" itemprop="dateCreated datePublished" datetime="2022-03-16T00:00:00+08:00">2022-03-16</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-09-13 15:15:31" itemprop="dateModified" datetime="2022-09-13T15:15:31+08:00">2022-09-13</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="CGLIB"><a href="#CGLIB" class="headerlink" title="CGLIB"></a>CGLIB</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CGLIB动态代理</span></span><br><span class="line"><span class="comment">// 1. 首先实现一个MethodInterceptor，方法调用会被转发到该类的intercept()方法。</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyMethodInterceptor</span> <span class="keyword">implements</span> <span class="title class_">MethodInterceptor</span>&#123;</span><br><span class="line">  ...</span><br><span class="line">	<span class="meta">@Override</span></span><br><span class="line">	<span class="keyword">public</span> Object <span class="title function_">intercept</span><span class="params">(Object obj, Method method, Object[] args, MethodProxy proxy)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">		logger.info(<span class="string">&quot;You said: &quot;</span> + Arrays.toString(args));</span><br><span class="line">		<span class="keyword">return</span> proxy.invokeSuper(obj, args);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 2. 然后在需要使用HelloConcrete的时候，通过CGLIB动态代理获取代理对象。</span></span><br><span class="line"><span class="type">Enhancer</span> <span class="variable">enhancer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Enhancer</span>();</span><br><span class="line">enhancer.setSuperclass(HelloConcrete.class);</span><br><span class="line">enhancer.setCallback(<span class="keyword">new</span> <span class="title class_">MyMethodInterceptor</span>());</span><br><span class="line"></span><br><span class="line"><span class="type">HelloConcrete</span> <span class="variable">hello</span> <span class="operator">=</span> (HelloConcrete)enhancer.create();</span><br><span class="line">System.out.println(hello.sayHello(<span class="string">&quot;I love you!&quot;</span>));</span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="优缺点比较"><a href="#优缺点比较" class="headerlink" title="优缺点比较"></a>优缺点比较</h2><p>JDK</p>
<ol>
<li><p>根据ClassLoader和Interface来获取接口类（前面已经讲了，类是由ClassLoader加载到JVM的，所以通过ClassLoader和Interface可以找到接口类）</p>
</li>
<li><p>获取构造对象；</p>
</li>
<li><p>通过构造对象和InvocationHandler生成实例，并返回，就是我们要的代理类。</p>
</li>
</ol>
<p>Java动态代理优缺点：</p>
<p>优点：</p>
<ol>
<li><p>Java本身支持，不用担心依赖问题，随着版本稳定升级；</p>
</li>
<li><p>代码实现简单；</p>
</li>
</ol>
<p>缺点：</p>
<ol>
<li><p>目标类必须实现某个接口，换言之，没有实现接口的类是不能生成代理对象的；</p>
</li>
<li><p>代理的方法必须都声明在接口中，否则，无法代理；</p>
</li>
<li><p>执行速度性能相对cglib较低；</p>
</li>
</ol>
<p>Cglib原理：</p>
<p>1.通过字节码增强技术动态的创建代理对象；</p>
<p>2.代理的是代理对象的引用；</p>
<p>Cglib优缺点：</p>
<p>优点：</p>
<p>1.代理的类无需实现接口；</p>
<p>2.执行速度相对JDK动态代理较高；</p>
<p>缺点：</p>
<p>1.字节码库需要进行更新以保证在新版java上能运行；</p>
<p>2.动态创建代理对象的代价相对JDK动态代理较高；</p>
<p>Tips：</p>
<p>1.代理的对象不能是final关键字修饰的</p>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/carpenterlee/p/8241042.html">https://www.cnblogs.com/carpenterlee/p/8241042.html</a></p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/brithToSpring/p/13356626.html">https://www.cnblogs.com/brithToSpring/p/13356626.html</a></p>
<p><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000023514746">https://segmentfault.com/a/1190000023514746</a></p>
</blockquote>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/15/DelayQueue/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/15/DelayQueue/" class="post-title-link" itemprop="url">DelayQueue</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>
      

      <time title="创建时间：2022-03-15 18:16:48 / 修改时间：19:41:02" itemprop="dateCreated datePublished" datetime="2022-03-15T18:16:48+08:00">2022-03-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/JUC/" itemprop="url" rel="index"><span itemprop="name">JUC</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p><strong>Delayed元素</strong>的<strong>无界</strong>阻塞队列，其中的<strong>元素只能在其延迟到期时被获取</strong>。 </p>
<p>队列的头部是延迟过期最早的那个Delayed元素。</p>
<p>如果没有延迟到期，则没有 head 并且poll将返回null 。</p>
<p>当元素的<code>getDelay(TimeUnit.NANOSECONDS)</code>方法返回小于或等于零的值时，就会发生过期。</p>
<p>尽管无法使用take或poll删除未过期的元素，它们仍被视为普通元素。 例如， size方法返回过期和未过期元素的计数。</p>
<p>此队列不允许空元素。</p>
<h1 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h1><p>实现了一个延时获取的元素类。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.jsy.threadPool;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.time.Duration;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.DelayQueue;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.Delayed;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: SongyangJi</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>:</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span>: 2021/10/31</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestQueue</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DelayedItem</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">Delayed</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> T element;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> delaySeconds;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> expireNanoTime;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">DelayedItem</span><span class="params">(T element, <span class="type">int</span> delaySeconds)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.element = element;</span><br><span class="line">            <span class="built_in">this</span>.delaySeconds = delaySeconds;</span><br><span class="line">            expireNanoTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(Duration.ofSeconds(delaySeconds));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 剩余的阻塞的时间</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getDelay</span><span class="params">(TimeUnit unit)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> expireNanoTime - System.nanoTime();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(Delayed o)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (o == <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">if</span> (o == <span class="built_in">this</span>)</span><br><span class="line">                <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">            <span class="type">long</span> <span class="variable">diff</span> <span class="operator">=</span> getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);</span><br><span class="line">            <span class="keyword">return</span> diff &gt; <span class="number">0</span> ? <span class="number">1</span> : diff == <span class="number">0</span> ? <span class="number">0</span> : -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;DelayedItem&#123;&quot;</span> +</span><br><span class="line">                    <span class="string">&quot;element=&quot;</span> + element +</span><br><span class="line">                    <span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> T <span class="title function_">getElement</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> element;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setElement</span><span class="params">(T element)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.element = element;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getDelaySeconds</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> delaySeconds;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        DelayQueue&lt;DelayedItem&lt;Integer&gt;&gt; delayQueue = <span class="keyword">new</span> <span class="title class_">DelayQueue</span>&lt;&gt;();</span><br><span class="line">        delayQueue.offer(<span class="keyword">new</span> <span class="title class_">DelayedItem</span>&lt;&gt;(<span class="number">1</span>, <span class="number">1</span>));</span><br><span class="line">        delayQueue.offer(<span class="keyword">new</span> <span class="title class_">DelayedItem</span>&lt;&gt;(<span class="number">1</span>, <span class="number">3</span>));</span><br><span class="line">        delayQueue.offer(<span class="keyword">new</span> <span class="title class_">DelayedItem</span>&lt;&gt;(<span class="number">1</span>, <span class="number">5</span>));</span><br><span class="line">        delayQueue.offer(<span class="keyword">new</span> <span class="title class_">DelayedItem</span>&lt;&gt;(<span class="number">1</span>, <span class="number">7</span>));</span><br><span class="line">        <span class="type">long</span> <span class="variable">s1</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            DelayedItem&lt;Integer&gt; item = delayQueue.take();</span><br><span class="line">            <span class="type">long</span> <span class="variable">s2</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">            System.out.println(<span class="string">&quot;to begin &quot;</span>+(s2 - s1)  + <span class="string">&quot; ms&quot;</span>);</span><br><span class="line">            System.out.println(item);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>





<h1 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> java.util.concurrent;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DelayQueue</span>&lt;E <span class="keyword">extends</span> <span class="title class_">Delayed</span>&gt; <span class="keyword">extends</span> <span class="title class_">AbstractQueue</span>&lt;E&gt;</span><br><span class="line">    <span class="keyword">implements</span> <span class="title class_">BlockingQueue</span>&lt;E&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">transient</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">   </span><br><span class="line">    <span class="comment">// 以”剩余的阻塞的时间“为优先级，也就是说队首的是最早到达延迟时间点的</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> PriorityQueue&lt;E&gt; q = <span class="keyword">new</span> <span class="title class_">PriorityQueue</span>&lt;E&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 用于等待首部元素的线程</span></span><br><span class="line">    <span class="keyword">private</span> Thread leader;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当新元素在队列头部可用或新线程可能需要成为领导者时发出条件信号</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">available</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">DelayQueue</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">offer</span><span class="params">(E e)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            q.offer(e);</span><br><span class="line">            <span class="keyword">if</span> (q.peek() == e) &#123;</span><br><span class="line">                leader = <span class="literal">null</span>;</span><br><span class="line">                available.signal(); <span class="comment">// 新元素在队列头部可用</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">poll</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">E</span> <span class="variable">first</span> <span class="operator">=</span> q.peek();</span><br><span class="line">            <span class="comment">// 如果队列为空，或者所有元素都没有到期，返回 null,返回第一个过期的元素</span></span><br><span class="line">            <span class="keyword">return</span> (first == <span class="literal">null</span> || first.getDelay(NANOSECONDS) &gt; <span class="number">0</span>)</span><br><span class="line">                ? <span class="literal">null</span></span><br><span class="line">                : q.poll();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">take</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">        lock.lockInterruptibly();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                <span class="type">E</span> <span class="variable">first</span> <span class="operator">=</span> q.peek();</span><br><span class="line">                <span class="keyword">if</span> (first == <span class="literal">null</span>)</span><br><span class="line">                    available.await();</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> first.getDelay(NANOSECONDS);</span><br><span class="line">                    <span class="keyword">if</span> (delay &lt;= <span class="number">0L</span>)</span><br><span class="line">                        <span class="keyword">return</span> q.poll(); <span class="comment">// 已经到时间了，可以出队</span></span><br><span class="line">                    first = <span class="literal">null</span>; <span class="comment">// don&#x27;t retain ref while waiting</span></span><br><span class="line">                  </span><br><span class="line">                    <span class="comment">// 第一个元素时间没到，take()方法的所在线程必须阻塞</span></span><br><span class="line">                  </span><br><span class="line">                    </span><br><span class="line">                    <span class="keyword">if</span> (leader != <span class="literal">null</span>)    <span class="comment">// 现在已经有其他线程在等待了</span></span><br><span class="line">                        available.await(); <span class="comment">// 于是这个它自己无限等待，等待其他线程唤醒它，见 #1 处</span></span><br><span class="line">                    <span class="keyword">else</span> &#123; <span class="comment">// #2</span></span><br><span class="line">                        <span class="type">Thread</span> <span class="variable">thisThread</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">                        leader = thisThread;</span><br><span class="line">                        <span class="keyword">try</span> &#123;</span><br><span class="line">                            available.awaitNanos(delay);  <span class="comment">// 等待delay时间段</span></span><br><span class="line">                        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                            <span class="keyword">if</span> (leader == thisThread)</span><br><span class="line">                                leader = <span class="literal">null</span>;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (leader == <span class="literal">null</span> &amp;&amp; q.peek() != <span class="literal">null</span>) <span class="comment">// 队列有元素，且没有leader，发出信号（leade r线程自己处理等待，获取元素，见 #2 处的代码段）</span></span><br><span class="line">                available.signal(); <span class="comment">// #1</span></span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 不管有没有到时间，都返回第一个元素</span></span><br><span class="line">    <span class="keyword">public</span> E <span class="title function_">peek</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> q.peek();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回所有元素的数量</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> q.size();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/01/Redis%E7%9A%84%E5%B8%B8%E8%A7%81%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/01/Redis%E7%9A%84%E5%B8%B8%E8%A7%81%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF/" class="post-title-link" itemprop="url">Redis的常见使用场景</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-01 11:22:31" itemprop="dateCreated datePublished" datetime="2022-03-01T11:22:31+08:00">2022-03-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-04-26 21:40:42" itemprop="dateModified" datetime="2022-04-26T21:40:42+08:00">2022-04-26</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Redis/" itemprop="url" rel="index"><span itemprop="name">Redis</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <ul>
<li>[1、缓存]</li>
</ul>
<p>String类型</p>
<p>例如：热点数据缓存（例如报表、头条排行榜、对象缓存、全页缓存等等可以提升为热点数据的访问数据。</p>
<ul>
<li>[2、数据共享分布式]</li>
</ul>
<p>举一个典型例子：分布式 session 的共享。</p>
<blockquote>
<p>spring session在 redis 里面保存的数据包括：</p>
<ul>
<li><p>SET 类型的</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">spring:session:expireations:[min]</span><br></pre></td></tr></table></figure>

<p>min 表示从 1970 年 1 月 1 日 0 点 0 分经过的分钟数， SET 集合的 member 为 expires:[sessionId] ,表示这一分钟应该过期的键。</p>
</li>
<li><p>String 类型的</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">spring:session:sessions:expires:[sessionId]</span><br></pre></td></tr></table></figure>

<p>该数据对应一个空值，表示 sessionId 过期的剩余时间，即 maxInactiveInterval。</p>
</li>
<li><p>Hash 类型的</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">spring:session:sessions:[sessionId]</span><br></pre></td></tr></table></figure>

<p>session 保存的数据，记录了 creationTime，maxInactiveInterval，lastAccessedTime，attribute。前两个数据是用于 session 过期管理的辅助数据结构。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;lastAccessedTime&quot;</span><span class="punctuation">:</span> <span class="number">1523933008926</span><span class="punctuation">,</span><span class="comment">/*2018/4/17 10:43:28*/</span></span><br><span class="line">    <span class="attr">&quot;creationTime&quot;</span><span class="punctuation">:</span> <span class="number">1523933008926</span><span class="punctuation">,</span> <span class="comment">/*2018/4/17 10:43:28*/</span></span><br><span class="line">    <span class="attr">&quot;maxInactiveInterval&quot;</span><span class="punctuation">:</span> <span class="number">1800</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;sessionAttr:name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xu&quot;</span> <span class="comment">// 用户数据</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li>
</ul>
</blockquote>
<p>基于 spring-session 解决分布式 session 共享问题:<a target="_blank" rel="noopener" href="https://spring.io/projects/spring-session-data-redis">https://spring.io/projects/spring-session-data-redis</a></p>
<p>Spring Session 数据结构解读:<a target="_blank" rel="noopener" href="https://blog.didispace.com/spring-session-source-learning-xjf/">https://blog.didispace.com/spring-session-source-learning-xjf/</a></p>
<p><a target="_blank" rel="noopener" href="https://spring.io/projects/spring-session-data-redis">https://spring.io/projects/spring-session-data-redis</a></p>
<ul>
<li>[3、分布式锁]</li>
</ul>
<p>基本原理就是 setnx+expire</p>
<ul>
<li>[4、全局ID]</li>
</ul>
<p>int类型，incrby，利用原子性</p>
<p>incrby userid 1000</p>
<p>分库分表的场景，一次性拿一段</p>
<ul>
<li>[5、计数器]</li>
</ul>
<p>int类型，incr方法</p>
<p>例如：文章的阅读量、微博点赞数、允许一定的延迟，先写入Redis再定时同步到数据库</p>
<ul>
<li>[6、限流]</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/jT0WYISuSoi_hkTmELb7PQ">https://mp.weixin.qq.com/s/jT0WYISuSoi_hkTmELb7PQ</a></p>
<ul>
<li>[7、位统计]</li>
</ul>
<p>这个可以用来做</p>
<ul>
<li>[8、购物车]</li>
<li>[9、用户消息时间线timeline]</li>
<li>[10、消息队列]</li>
<li>[11、抽奖]</li>
<li>[12、点赞、签到、打卡]</li>
<li>[13、商品标签]</li>
<li>[14、商品筛选]</li>
<li>[15、用户关注、推荐模型]</li>
<li>[16、排行榜]</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://example.com/2022/03/01/Goroutine%E3%80%81Channel%E3%80%81sync/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="SongyangJi">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="JsyBlog">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="post" itemscope itemtype="http://schema.org/CreativeWork">
      <meta itemprop="name" content="undefined | JsyBlog">
      <meta itemprop="description" content="">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/01/Goroutine%E3%80%81Channel%E3%80%81sync/" class="post-title-link" itemprop="url">Go并发编程——goroutine、channel、sync</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-01 10:35:00" itemprop="dateCreated datePublished" datetime="2022-03-01T10:35:00+08:00">2022-03-01</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar-check"></i>
      </span>
      <span class="post-meta-item-text">更新于</span>
      <time title="修改时间：2022-03-15 19:45:58" itemprop="dateModified" datetime="2022-03-15T19:45:58+08:00">2022-03-15</time>
    </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Go/" itemprop="url" rel="index"><span itemprop="name">Go</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 它的调度也是不规律的</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">f</span><span class="params">(id <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	cnt := <span class="number">0</span></span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		time.Sleep(time.Duration(rand.Intn(<span class="number">1000</span>)) * time.Millisecond)</span><br><span class="line">		cnt++</span><br><span class="line">		fmt.Printf(<span class="string">&quot;%d says %d\n&quot;</span>, id, cnt)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	<span class="keyword">go</span> f(<span class="number">1</span>)</span><br><span class="line">	<span class="keyword">go</span> f(<span class="number">2</span>)</span><br><span class="line">	<span class="keyword">go</span> f(<span class="number">3</span>)</span><br><span class="line">  <span class="comment">// 仅仅让主任务不退出而已</span></span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>





<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> wg sync.WaitGroup</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">hello</span><span class="params">(i <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">	<span class="keyword">defer</span> wg.Done() <span class="comment">// goroutine结束就登记-1</span></span><br><span class="line">	fmt.Println(<span class="string">&quot;Hello Goroutine!&quot;</span>, i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line">		wg.Add(<span class="number">1</span>) <span class="comment">// 启动一个goroutine就登记+1</span></span><br><span class="line">		<span class="keyword">go</span> hello(i) <span class="comment">// 10个goroutine是并发执行的</span></span><br><span class="line">	&#125;</span><br><span class="line">	wg.Wait() <span class="comment">// 等待所有登记的goroutine都结束</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




  <nav class="pagination">
    <a class="extend prev" rel="prev" title="上一页" aria-label="上一页" href="/page/12/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/12/">12</a><span class="page-number current">13</span><a class="page-number" href="/page/14/">14</a><span class="space">&hellip;</span><a class="page-number" href="/page/26/">26</a><a class="extend next" rel="next" title="下一页" aria-label="下一页" href="/page/14/"><i class="fa fa-angle-right"></i></a>
  </nav>

</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">SongyangJi</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/muse/" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>
  <div class="sidebar-dimmer"></div>
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up fa-lg"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/hexo-generator-searchdb/1.4.1/search.js" integrity="sha256-1kfA5uHPf65M5cphT2dvymhkuyHPQp5A53EGZOnOLmc=" crossorigin="anonymous"></script>
<script src="/js/third-party/search/local-search.js"></script>





  





</body>
</html>
